jazz-tools 0.19.1 → 0.19.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.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +48 -48
  2. package/CHANGELOG.md +11 -0
  3. package/dist/inspector/{custom-element-QESCMFY7.js → custom-element-ABVPHX53.js} +1118 -465
  4. package/dist/inspector/custom-element-ABVPHX53.js.map +1 -0
  5. package/dist/inspector/index.js +1090 -437
  6. package/dist/inspector/index.js.map +1 -1
  7. package/dist/inspector/register-custom-element.js +1 -1
  8. package/dist/inspector/tests/utils/history.test.d.ts +2 -0
  9. package/dist/inspector/tests/utils/history.test.d.ts.map +1 -0
  10. package/dist/inspector/tests/viewer/co-value-editor.test.d.ts +2 -0
  11. package/dist/inspector/tests/viewer/co-value-editor.test.d.ts.map +1 -0
  12. package/dist/inspector/tests/viewer/comap-view.test.d.ts +2 -0
  13. package/dist/inspector/tests/viewer/comap-view.test.d.ts.map +1 -0
  14. package/dist/inspector/ui/icon.d.ts +6 -0
  15. package/dist/inspector/ui/icon.d.ts.map +1 -1
  16. package/dist/inspector/ui/icons/add-icon.d.ts +2 -0
  17. package/dist/inspector/ui/icons/add-icon.d.ts.map +1 -0
  18. package/dist/inspector/ui/icons/edit-icon.d.ts +2 -0
  19. package/dist/inspector/ui/icons/edit-icon.d.ts.map +1 -0
  20. package/dist/inspector/ui/icons/history.d.ts +2 -0
  21. package/dist/inspector/ui/icons/history.d.ts.map +1 -0
  22. package/dist/inspector/utils/history.d.ts +3 -0
  23. package/dist/inspector/utils/history.d.ts.map +1 -0
  24. package/dist/inspector/utils/transactions-changes.d.ts +38 -0
  25. package/dist/inspector/utils/transactions-changes.d.ts.map +1 -0
  26. package/dist/inspector/viewer/co-map-view.d.ts +9 -0
  27. package/dist/inspector/viewer/co-map-view.d.ts.map +1 -0
  28. package/dist/inspector/viewer/co-value-editor.d.ts +10 -0
  29. package/dist/inspector/viewer/co-value-editor.d.ts.map +1 -0
  30. package/dist/inspector/viewer/grid-view.d.ts +3 -2
  31. package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
  32. package/dist/inspector/viewer/history-view.d.ts.map +1 -1
  33. package/dist/inspector/viewer/page.d.ts.map +1 -1
  34. package/dist/tools/coValues/CoFieldInit.d.ts +2 -1
  35. package/dist/tools/coValues/CoFieldInit.d.ts.map +1 -1
  36. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts +3 -2
  37. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts.map +1 -1
  38. package/package.json +4 -4
  39. package/src/inspector/tests/utils/history.test.ts +401 -0
  40. package/src/inspector/tests/viewer/co-value-editor.test.tsx +903 -0
  41. package/src/inspector/tests/viewer/comap-view.test.tsx +581 -0
  42. package/src/inspector/ui/icon.tsx +6 -0
  43. package/src/inspector/ui/icons/add-icon.tsx +21 -0
  44. package/src/inspector/ui/icons/edit-icon.tsx +17 -0
  45. package/src/inspector/ui/icons/history.tsx +28 -0
  46. package/src/inspector/ui/modal.tsx +3 -3
  47. package/src/inspector/utils/history.ts +49 -0
  48. package/src/inspector/utils/transactions-changes.ts +98 -0
  49. package/src/inspector/viewer/co-map-view.tsx +312 -0
  50. package/src/inspector/viewer/co-value-editor.tsx +164 -0
  51. package/src/inspector/viewer/grid-view.tsx +139 -10
  52. package/src/inspector/viewer/history-view.tsx +16 -118
  53. package/src/inspector/viewer/page.tsx +13 -0
  54. package/src/react-core/tests/usePassPhraseAuth.test.ts +1 -1
  55. package/src/tools/coValues/CoFieldInit.ts +6 -3
  56. package/src/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.ts +12 -7
  57. package/src/tools/tests/coVector.test.ts +43 -0
  58. package/dist/inspector/custom-element-QESCMFY7.js.map +0 -1
@@ -0,0 +1,903 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
3
+ import { setActiveAccount, setupJazzTestSync } from "jazz-tools/testing";
4
+ import { co, z } from "jazz-tools";
5
+ import {
6
+ cleanup,
7
+ fireEvent,
8
+ render,
9
+ screen,
10
+ waitFor,
11
+ } from "@testing-library/react";
12
+ import { CoValueEditor } from "../../viewer/co-value-editor";
13
+ import { setup } from "goober";
14
+ import React from "react";
15
+
16
+ describe("CoValueEditor", async () => {
17
+ const account = await setupJazzTestSync();
18
+ setActiveAccount(account);
19
+
20
+ beforeAll(() => {
21
+ setup(React.createElement);
22
+ });
23
+
24
+ afterEach(() => {
25
+ cleanup();
26
+ });
27
+
28
+ describe("Initial Rendering", () => {
29
+ it("should render with number value", async () => {
30
+ const value = co
31
+ .map({
32
+ count: z.number(),
33
+ })
34
+ .create({ count: 42 });
35
+
36
+ const onCancel = vi.fn();
37
+
38
+ render(
39
+ <CoValueEditor
40
+ node={account.$jazz.localNode}
41
+ property="count"
42
+ value={42}
43
+ coValue={value.$jazz.raw}
44
+ onCancel={onCancel}
45
+ />,
46
+ );
47
+
48
+ expect(screen.getByLabelText("Type")).toBeDefined();
49
+ expect(screen.getByDisplayValue("number")).toBeDefined();
50
+ expect(screen.getByDisplayValue("42")).toBeDefined();
51
+ expect(screen.getByText("Cancel")).toBeDefined();
52
+ expect(screen.getByText("Submit")).toBeDefined();
53
+ });
54
+
55
+ it("should render with string value", async () => {
56
+ const value = co
57
+ .map({
58
+ name: z.string(),
59
+ })
60
+ .create({ name: "test" });
61
+
62
+ const onCancel = vi.fn();
63
+
64
+ render(
65
+ <CoValueEditor
66
+ node={account.$jazz.localNode}
67
+ property="name"
68
+ value="test"
69
+ coValue={value.$jazz.raw}
70
+ onCancel={onCancel}
71
+ />,
72
+ );
73
+
74
+ expect(screen.getByDisplayValue("string")).toBeDefined();
75
+ expect(screen.getByDisplayValue("test")).toBeDefined();
76
+ });
77
+
78
+ it("should render with boolean true value", async () => {
79
+ const value = co
80
+ .map({
81
+ active: z.boolean(),
82
+ })
83
+ .create({ active: true });
84
+
85
+ const onCancel = vi.fn();
86
+
87
+ render(
88
+ <CoValueEditor
89
+ node={account.$jazz.localNode}
90
+ property="active"
91
+ value={true}
92
+ coValue={value.$jazz.raw}
93
+ onCancel={onCancel}
94
+ />,
95
+ );
96
+
97
+ expect(screen.getByDisplayValue("true")).toBeDefined();
98
+ expect(screen.queryByRole("textbox")).toBeNull();
99
+ });
100
+
101
+ it("should render with boolean false value", async () => {
102
+ const value = co
103
+ .map({
104
+ active: z.boolean(),
105
+ })
106
+ .create({ active: false });
107
+
108
+ const onCancel = vi.fn();
109
+
110
+ render(
111
+ <CoValueEditor
112
+ node={account.$jazz.localNode}
113
+ property="active"
114
+ value={false}
115
+ coValue={value.$jazz.raw}
116
+ onCancel={onCancel}
117
+ />,
118
+ );
119
+
120
+ expect(screen.getByDisplayValue("false")).toBeDefined();
121
+ expect(screen.queryByRole("textbox")).toBeNull();
122
+ });
123
+
124
+ it("should render with null value", async () => {
125
+ const value = co
126
+ .map({
127
+ data: z.string().nullable(),
128
+ })
129
+ .create({ data: null });
130
+
131
+ const onCancel = vi.fn();
132
+
133
+ render(
134
+ <CoValueEditor
135
+ node={account.$jazz.localNode}
136
+ property="data"
137
+ value={null}
138
+ coValue={value.$jazz.raw}
139
+ onCancel={onCancel}
140
+ />,
141
+ );
142
+
143
+ expect(screen.getByDisplayValue("null")).toBeDefined();
144
+ expect(screen.queryByRole("textbox")).toBeNull();
145
+ });
146
+
147
+ it("should render with undefined value", async () => {
148
+ const value = co
149
+ .map({
150
+ optional: z.string().optional(),
151
+ })
152
+ .create({});
153
+
154
+ const onCancel = vi.fn();
155
+
156
+ render(
157
+ <CoValueEditor
158
+ node={account.$jazz.localNode}
159
+ property="optional"
160
+ value={undefined}
161
+ coValue={value.$jazz.raw}
162
+ onCancel={onCancel}
163
+ />,
164
+ );
165
+
166
+ expect(screen.getByDisplayValue("undefined")).toBeDefined();
167
+ expect(screen.queryByRole("textbox")).toBeNull();
168
+ });
169
+
170
+ it("should render with object value", async () => {
171
+ const value = co
172
+ .map({
173
+ config: z.json(),
174
+ })
175
+ .create({ config: { foo: "bar", num: 123 } });
176
+
177
+ const onCancel = vi.fn();
178
+
179
+ render(
180
+ <CoValueEditor
181
+ node={account.$jazz.localNode}
182
+ property="config"
183
+ value={{ foo: "bar", num: 123 }}
184
+ coValue={value.$jazz.raw}
185
+ onCancel={onCancel}
186
+ />,
187
+ );
188
+
189
+ expect(screen.getByDisplayValue("object")).toBeDefined();
190
+ const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
191
+ expect(textarea).toBeDefined();
192
+ expect(textarea.value).toBe(
193
+ JSON.stringify({ foo: "bar", num: 123 }, null, 2),
194
+ );
195
+ });
196
+ });
197
+
198
+ describe("Type Selection", () => {
199
+ it("should show textarea when number type is selected", async () => {
200
+ const value = co
201
+ .map({
202
+ count: z.number(),
203
+ })
204
+ .create({ count: 42 });
205
+
206
+ const onCancel = vi.fn();
207
+
208
+ render(
209
+ <CoValueEditor
210
+ node={account.$jazz.localNode}
211
+ property="count"
212
+ value={42}
213
+ coValue={value.$jazz.raw}
214
+ onCancel={onCancel}
215
+ />,
216
+ );
217
+
218
+ const select = screen.getByLabelText("Type");
219
+ expect(screen.getByRole("textbox")).toBeDefined();
220
+
221
+ fireEvent.change(select, { target: { value: "number" } });
222
+ expect(screen.getByRole("textbox")).toBeDefined();
223
+ });
224
+
225
+ it("should show textarea when string type is selected", async () => {
226
+ const value = co
227
+ .map({
228
+ name: z.string(),
229
+ })
230
+ .create({ name: "test" });
231
+
232
+ const onCancel = vi.fn();
233
+
234
+ render(
235
+ <CoValueEditor
236
+ node={account.$jazz.localNode}
237
+ property="name"
238
+ value="test"
239
+ coValue={value.$jazz.raw}
240
+ onCancel={onCancel}
241
+ />,
242
+ );
243
+
244
+ const select = screen.getByLabelText("Type");
245
+ expect(screen.getByRole("textbox")).toBeDefined();
246
+
247
+ fireEvent.change(select, { target: { value: "string" } });
248
+ expect(screen.getByRole("textbox")).toBeDefined();
249
+ });
250
+
251
+ it("should show textarea when object type is selected", async () => {
252
+ const value = co
253
+ .map({
254
+ data: z.json(),
255
+ })
256
+ .create({ data: {} });
257
+
258
+ const onCancel = vi.fn();
259
+
260
+ render(
261
+ <CoValueEditor
262
+ node={account.$jazz.localNode}
263
+ property="data"
264
+ value={{}}
265
+ coValue={value.$jazz.raw}
266
+ onCancel={onCancel}
267
+ />,
268
+ );
269
+
270
+ const select = screen.getByLabelText("Type");
271
+ expect(screen.getByRole("textbox")).toBeDefined();
272
+
273
+ fireEvent.change(select, { target: { value: "object" } });
274
+ expect(screen.getByRole("textbox")).toBeDefined();
275
+ });
276
+
277
+ it("should hide textarea when boolean type is selected", async () => {
278
+ const value = co
279
+ .map({
280
+ count: z.number(),
281
+ })
282
+ .create({ count: 42 });
283
+
284
+ const onCancel = vi.fn();
285
+
286
+ render(
287
+ <CoValueEditor
288
+ node={account.$jazz.localNode}
289
+ property="count"
290
+ value={42}
291
+ coValue={value.$jazz.raw}
292
+ onCancel={onCancel}
293
+ />,
294
+ );
295
+
296
+ const select = screen.getByLabelText("Type");
297
+ fireEvent.change(select, { target: { value: "true" } });
298
+
299
+ await waitFor(() => {
300
+ expect(screen.queryByRole("textbox")).toBeNull();
301
+ });
302
+ });
303
+
304
+ it("should hide textarea when null type is selected", async () => {
305
+ const value = co
306
+ .map({
307
+ count: z.number(),
308
+ })
309
+ .create({ count: 42 });
310
+
311
+ const onCancel = vi.fn();
312
+
313
+ render(
314
+ <CoValueEditor
315
+ node={account.$jazz.localNode}
316
+ property="count"
317
+ value={42}
318
+ coValue={value.$jazz.raw}
319
+ onCancel={onCancel}
320
+ />,
321
+ );
322
+
323
+ const select = screen.getByLabelText("Type");
324
+ fireEvent.change(select, { target: { value: "null" } });
325
+
326
+ await waitFor(() => {
327
+ expect(screen.queryByRole("textbox")).toBeNull();
328
+ });
329
+ });
330
+
331
+ it("should hide textarea when undefined type is selected", async () => {
332
+ const value = co
333
+ .map({
334
+ count: z.number(),
335
+ })
336
+ .create({ count: 42 });
337
+
338
+ const onCancel = vi.fn();
339
+
340
+ render(
341
+ <CoValueEditor
342
+ node={account.$jazz.localNode}
343
+ property="count"
344
+ value={42}
345
+ coValue={value.$jazz.raw}
346
+ onCancel={onCancel}
347
+ />,
348
+ );
349
+
350
+ const select = screen.getByLabelText("Type");
351
+ fireEvent.change(select, { target: { value: "undefined" } });
352
+
353
+ await waitFor(() => {
354
+ expect(screen.queryByRole("textbox")).toBeNull();
355
+ });
356
+ });
357
+ });
358
+
359
+ describe("Form Submission", () => {
360
+ it("should submit number value", async () => {
361
+ const value = co
362
+ .map({
363
+ count: z.number(),
364
+ })
365
+ .create({ count: 42 });
366
+
367
+ const onCancel = vi.fn();
368
+ const makeTransactionSpy = vi.spyOn(
369
+ value.$jazz.raw.core,
370
+ "makeTransaction",
371
+ );
372
+
373
+ render(
374
+ <CoValueEditor
375
+ node={account.$jazz.localNode}
376
+ property="count"
377
+ value={42}
378
+ coValue={value.$jazz.raw}
379
+ onCancel={onCancel}
380
+ />,
381
+ );
382
+
383
+ const textarea = screen.getByRole("textbox");
384
+ fireEvent.change(textarea, { target: { value: "100" } });
385
+
386
+ const submitButton = screen.getByText("Submit");
387
+ fireEvent.click(submitButton);
388
+
389
+ await waitFor(() => {
390
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
391
+ [{ op: "set", key: "count", value: 100 }],
392
+ "private",
393
+ );
394
+ expect(onCancel).toHaveBeenCalled();
395
+ });
396
+ });
397
+
398
+ it("should submit string value", async () => {
399
+ const value = co
400
+ .map({
401
+ name: z.string(),
402
+ })
403
+ .create({ name: "test" });
404
+
405
+ const onCancel = vi.fn();
406
+ const makeTransactionSpy = vi.spyOn(
407
+ value.$jazz.raw.core,
408
+ "makeTransaction",
409
+ );
410
+
411
+ render(
412
+ <CoValueEditor
413
+ node={account.$jazz.localNode}
414
+ property="name"
415
+ value="test"
416
+ coValue={value.$jazz.raw}
417
+ onCancel={onCancel}
418
+ />,
419
+ );
420
+
421
+ const textarea = screen.getByRole("textbox");
422
+ fireEvent.change(textarea, { target: { value: "updated" } });
423
+
424
+ const submitButton = screen.getByText("Submit");
425
+ fireEvent.click(submitButton);
426
+
427
+ await waitFor(() => {
428
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
429
+ [{ op: "set", key: "name", value: "updated" }],
430
+ "private",
431
+ );
432
+ expect(onCancel).toHaveBeenCalled();
433
+ });
434
+ });
435
+
436
+ it("should submit boolean true value", async () => {
437
+ const value = co
438
+ .map({
439
+ active: z.boolean(),
440
+ })
441
+ .create({ active: false });
442
+
443
+ const onCancel = vi.fn();
444
+ const makeTransactionSpy = vi.spyOn(
445
+ value.$jazz.raw.core,
446
+ "makeTransaction",
447
+ );
448
+
449
+ render(
450
+ <CoValueEditor
451
+ node={account.$jazz.localNode}
452
+ property="active"
453
+ value={false}
454
+ coValue={value.$jazz.raw}
455
+ onCancel={onCancel}
456
+ />,
457
+ );
458
+
459
+ const select = screen.getByLabelText("Type");
460
+ fireEvent.change(select, { target: { value: "true" } });
461
+
462
+ const submitButton = screen.getByText("Submit");
463
+ fireEvent.click(submitButton);
464
+
465
+ await waitFor(() => {
466
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
467
+ [{ op: "set", key: "active", value: true }],
468
+ "private",
469
+ );
470
+ expect(onCancel).toHaveBeenCalled();
471
+ });
472
+ });
473
+
474
+ it("should submit boolean false value", async () => {
475
+ const value = co
476
+ .map({
477
+ active: z.boolean(),
478
+ })
479
+ .create({ active: true });
480
+
481
+ const onCancel = vi.fn();
482
+ const makeTransactionSpy = vi.spyOn(
483
+ value.$jazz.raw.core,
484
+ "makeTransaction",
485
+ );
486
+
487
+ render(
488
+ <CoValueEditor
489
+ node={account.$jazz.localNode}
490
+ property="active"
491
+ value={true}
492
+ coValue={value.$jazz.raw}
493
+ onCancel={onCancel}
494
+ />,
495
+ );
496
+
497
+ const select = screen.getByLabelText("Type");
498
+ fireEvent.change(select, { target: { value: "false" } });
499
+
500
+ const submitButton = screen.getByText("Submit");
501
+ fireEvent.click(submitButton);
502
+
503
+ await waitFor(() => {
504
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
505
+ [{ op: "set", key: "active", value: false }],
506
+ "private",
507
+ );
508
+ expect(onCancel).toHaveBeenCalled();
509
+ });
510
+ });
511
+
512
+ it("should submit null value", async () => {
513
+ const value = co
514
+ .map({
515
+ data: z.string().nullable(),
516
+ })
517
+ .create({ data: "test" });
518
+
519
+ const onCancel = vi.fn();
520
+ const makeTransactionSpy = vi.spyOn(
521
+ value.$jazz.raw.core,
522
+ "makeTransaction",
523
+ );
524
+
525
+ render(
526
+ <CoValueEditor
527
+ node={account.$jazz.localNode}
528
+ property="data"
529
+ value="test"
530
+ coValue={value.$jazz.raw}
531
+ onCancel={onCancel}
532
+ />,
533
+ );
534
+
535
+ const select = screen.getByLabelText("Type");
536
+ fireEvent.change(select, { target: { value: "null" } });
537
+
538
+ const submitButton = screen.getByText("Submit");
539
+ fireEvent.click(submitButton);
540
+
541
+ await waitFor(() => {
542
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
543
+ [{ op: "set", key: "data", value: null }],
544
+ "private",
545
+ );
546
+ expect(onCancel).toHaveBeenCalled();
547
+ });
548
+ });
549
+
550
+ it("should submit undefined value", async () => {
551
+ const value = co
552
+ .map({
553
+ optional: z.string().optional(),
554
+ })
555
+ .create({ optional: "test" });
556
+
557
+ const onCancel = vi.fn();
558
+ const makeTransactionSpy = vi.spyOn(
559
+ value.$jazz.raw.core,
560
+ "makeTransaction",
561
+ );
562
+
563
+ render(
564
+ <CoValueEditor
565
+ node={account.$jazz.localNode}
566
+ property="optional"
567
+ value="test"
568
+ coValue={value.$jazz.raw}
569
+ onCancel={onCancel}
570
+ />,
571
+ );
572
+
573
+ const select = screen.getByLabelText("Type");
574
+ fireEvent.change(select, { target: { value: "undefined" } });
575
+
576
+ const submitButton = screen.getByText("Submit");
577
+ fireEvent.click(submitButton);
578
+
579
+ await waitFor(() => {
580
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
581
+ [{ op: "set", key: "optional", value: undefined }],
582
+ "private",
583
+ );
584
+ expect(onCancel).toHaveBeenCalled();
585
+ });
586
+ });
587
+
588
+ it("should submit object value", async () => {
589
+ const value = co
590
+ .map({
591
+ config: z.json(),
592
+ })
593
+ .create({ config: {} });
594
+
595
+ const onCancel = vi.fn();
596
+ const makeTransactionSpy = vi.spyOn(
597
+ value.$jazz.raw.core,
598
+ "makeTransaction",
599
+ );
600
+
601
+ render(
602
+ <CoValueEditor
603
+ node={account.$jazz.localNode}
604
+ property="config"
605
+ value={{}}
606
+ coValue={value.$jazz.raw}
607
+ onCancel={onCancel}
608
+ />,
609
+ );
610
+
611
+ const textarea = screen.getByRole("textbox");
612
+ const newObject = { foo: "bar", nested: { value: 123 } };
613
+ fireEvent.change(textarea, {
614
+ target: { value: JSON.stringify(newObject, null, 2) },
615
+ });
616
+
617
+ const submitButton = screen.getByText("Submit");
618
+ fireEvent.click(submitButton);
619
+
620
+ await waitFor(() => {
621
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
622
+ [{ op: "set", key: "config", value: newObject }],
623
+ "private",
624
+ );
625
+ expect(onCancel).toHaveBeenCalled();
626
+ });
627
+ });
628
+
629
+ it("should prevent default form submission", async () => {
630
+ const value = co
631
+ .map({
632
+ count: z.number(),
633
+ })
634
+ .create({ count: 42 });
635
+
636
+ const onCancel = vi.fn();
637
+
638
+ render(
639
+ <CoValueEditor
640
+ node={account.$jazz.localNode}
641
+ property="count"
642
+ value={42}
643
+ coValue={value.$jazz.raw}
644
+ onCancel={onCancel}
645
+ />,
646
+ );
647
+
648
+ const form = screen.getByRole("textbox").closest("form");
649
+ expect(form).toBeDefined();
650
+
651
+ const submitEvent = new Event("submit", {
652
+ bubbles: true,
653
+ cancelable: true,
654
+ });
655
+ const preventDefaultSpy = vi.spyOn(submitEvent, "preventDefault");
656
+ const stopPropagationSpy = vi.spyOn(submitEvent, "stopPropagation");
657
+
658
+ if (form) {
659
+ fireEvent(form, submitEvent);
660
+ }
661
+
662
+ expect(preventDefaultSpy).toHaveBeenCalled();
663
+ expect(stopPropagationSpy).toHaveBeenCalled();
664
+ });
665
+ });
666
+
667
+ describe("Cancel Button", () => {
668
+ it("should call onCancel when cancel button is clicked", async () => {
669
+ const value = co
670
+ .map({
671
+ count: z.number(),
672
+ })
673
+ .create({ count: 42 });
674
+
675
+ const onCancel = vi.fn();
676
+
677
+ render(
678
+ <CoValueEditor
679
+ node={account.$jazz.localNode}
680
+ property="count"
681
+ value={42}
682
+ coValue={value.$jazz.raw}
683
+ onCancel={onCancel}
684
+ />,
685
+ );
686
+
687
+ const cancelButton = screen.getByText("Cancel");
688
+ fireEvent.click(cancelButton);
689
+
690
+ expect(onCancel).toHaveBeenCalledTimes(1);
691
+ });
692
+
693
+ it("should not make transaction when cancel is clicked", async () => {
694
+ const value = co
695
+ .map({
696
+ count: z.number(),
697
+ })
698
+ .create({ count: 42 });
699
+
700
+ const onCancel = vi.fn();
701
+ const makeTransactionSpy = vi.spyOn(
702
+ value.$jazz.raw.core,
703
+ "makeTransaction",
704
+ );
705
+
706
+ render(
707
+ <CoValueEditor
708
+ node={account.$jazz.localNode}
709
+ property="count"
710
+ value={42}
711
+ coValue={value.$jazz.raw}
712
+ onCancel={onCancel}
713
+ />,
714
+ );
715
+
716
+ const cancelButton = screen.getByText("Cancel");
717
+ fireEvent.click(cancelButton);
718
+
719
+ expect(makeTransactionSpy).not.toHaveBeenCalled();
720
+ });
721
+ });
722
+
723
+ describe("Event Propagation", () => {
724
+ it("should stop propagation on select click", async () => {
725
+ const value = co
726
+ .map({
727
+ count: z.number(),
728
+ })
729
+ .create({ count: 42 });
730
+
731
+ const onCancel = vi.fn();
732
+
733
+ render(
734
+ <CoValueEditor
735
+ node={account.$jazz.localNode}
736
+ property="count"
737
+ value={42}
738
+ coValue={value.$jazz.raw}
739
+ onCancel={onCancel}
740
+ />,
741
+ );
742
+
743
+ const select = screen.getByLabelText("Type");
744
+ const clickEvent = new MouseEvent("click", { bubbles: true });
745
+ const stopPropagationSpy = vi.spyOn(clickEvent, "stopPropagation");
746
+
747
+ fireEvent(select, clickEvent);
748
+
749
+ expect(stopPropagationSpy).toHaveBeenCalled();
750
+ });
751
+
752
+ it("should stop propagation on textarea click", async () => {
753
+ const value = co
754
+ .map({
755
+ count: z.number(),
756
+ })
757
+ .create({ count: 42 });
758
+
759
+ const onCancel = vi.fn();
760
+
761
+ render(
762
+ <CoValueEditor
763
+ node={account.$jazz.localNode}
764
+ property="count"
765
+ value={42}
766
+ coValue={value.$jazz.raw}
767
+ onCancel={onCancel}
768
+ />,
769
+ );
770
+
771
+ const textarea = screen.getByRole("textbox");
772
+ const clickEvent = new MouseEvent("click", { bubbles: true });
773
+ const stopPropagationSpy = vi.spyOn(clickEvent, "stopPropagation");
774
+
775
+ fireEvent(textarea, clickEvent);
776
+
777
+ expect(stopPropagationSpy).toHaveBeenCalled();
778
+ });
779
+ });
780
+
781
+ describe("Edge Cases", () => {
782
+ it("should handle decimal number input", async () => {
783
+ const value = co
784
+ .map({
785
+ count: z.number(),
786
+ })
787
+ .create({ count: 42 });
788
+
789
+ const onCancel = vi.fn();
790
+ const makeTransactionSpy = vi.spyOn(
791
+ value.$jazz.raw.core,
792
+ "makeTransaction",
793
+ );
794
+
795
+ render(
796
+ <CoValueEditor
797
+ node={account.$jazz.localNode}
798
+ property="count"
799
+ value={42}
800
+ coValue={value.$jazz.raw}
801
+ onCancel={onCancel}
802
+ />,
803
+ );
804
+
805
+ const textarea = screen.getByRole("textbox");
806
+ fireEvent.change(textarea, { target: { value: "3.14" } });
807
+
808
+ const submitButton = screen.getByText("Submit");
809
+ fireEvent.click(submitButton);
810
+
811
+ await waitFor(() => {
812
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
813
+ [{ op: "set", key: "count", value: 3.14 }],
814
+ "private",
815
+ );
816
+ });
817
+ });
818
+
819
+ it("should handle empty string input", async () => {
820
+ const value = co
821
+ .map({
822
+ name: z.string(),
823
+ })
824
+ .create({ name: "test" });
825
+
826
+ const onCancel = vi.fn();
827
+ const makeTransactionSpy = vi.spyOn(
828
+ value.$jazz.raw.core,
829
+ "makeTransaction",
830
+ );
831
+
832
+ render(
833
+ <CoValueEditor
834
+ node={account.$jazz.localNode}
835
+ property="name"
836
+ value="test"
837
+ coValue={value.$jazz.raw}
838
+ onCancel={onCancel}
839
+ />,
840
+ );
841
+
842
+ const textarea = screen.getByRole("textbox");
843
+ fireEvent.change(textarea, { target: { value: "" } });
844
+
845
+ const submitButton = screen.getByText("Submit");
846
+ fireEvent.click(submitButton);
847
+
848
+ await waitFor(() => {
849
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
850
+ [{ op: "set", key: "name", value: "" }],
851
+ "private",
852
+ );
853
+ });
854
+ });
855
+
856
+ it("should handle complex nested object", async () => {
857
+ const value = co
858
+ .map({
859
+ config: z.json(),
860
+ })
861
+ .create({ config: {} });
862
+
863
+ const onCancel = vi.fn();
864
+ const makeTransactionSpy = vi.spyOn(
865
+ value.$jazz.raw.core,
866
+ "makeTransaction",
867
+ );
868
+
869
+ render(
870
+ <CoValueEditor
871
+ node={account.$jazz.localNode}
872
+ property="config"
873
+ value={{}}
874
+ coValue={value.$jazz.raw}
875
+ onCancel={onCancel}
876
+ />,
877
+ );
878
+
879
+ const textarea = screen.getByRole("textbox");
880
+ const complexObject = {
881
+ nested: {
882
+ deep: {
883
+ value: [1, 2, 3],
884
+ items: [{ id: 1 }, { id: 2 }],
885
+ },
886
+ },
887
+ };
888
+ fireEvent.change(textarea, {
889
+ target: { value: JSON.stringify(complexObject, null, 2) },
890
+ });
891
+
892
+ const submitButton = screen.getByText("Submit");
893
+ fireEvent.click(submitButton);
894
+
895
+ await waitFor(() => {
896
+ expect(makeTransactionSpy).toHaveBeenCalledWith(
897
+ [{ op: "set", key: "config", value: complexObject }],
898
+ "private",
899
+ );
900
+ });
901
+ });
902
+ });
903
+ });