jazz-tools 0.13.17 → 0.13.18

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 (82) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +11 -0
  3. package/dist/{chunk-PYBQOYML.js → chunk-ITSHLDQB.js} +729 -595
  4. package/dist/chunk-ITSHLDQB.js.map +1 -0
  5. package/dist/coValues/account.d.ts +5 -4
  6. package/dist/coValues/account.d.ts.map +1 -1
  7. package/dist/coValues/coFeed.d.ts +1 -0
  8. package/dist/coValues/coFeed.d.ts.map +1 -1
  9. package/dist/coValues/coList.d.ts +1 -0
  10. package/dist/coValues/coList.d.ts.map +1 -1
  11. package/dist/coValues/coMap.d.ts +4 -2
  12. package/dist/coValues/coMap.d.ts.map +1 -1
  13. package/dist/coValues/coPlainText.d.ts +2 -2
  14. package/dist/coValues/coPlainText.d.ts.map +1 -1
  15. package/dist/coValues/deepLoading.d.ts +1 -9
  16. package/dist/coValues/deepLoading.d.ts.map +1 -1
  17. package/dist/coValues/extensions/imageDef.d.ts.map +1 -1
  18. package/dist/coValues/group.d.ts.map +1 -1
  19. package/dist/coValues/inbox.d.ts.map +1 -1
  20. package/dist/coValues/interfaces.d.ts +4 -1
  21. package/dist/coValues/interfaces.d.ts.map +1 -1
  22. package/dist/implementation/createContext.d.ts.map +1 -1
  23. package/dist/implementation/refs.d.ts +5 -10
  24. package/dist/implementation/refs.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/internal.d.ts +1 -1
  27. package/dist/internal.d.ts.map +1 -1
  28. package/dist/subscribe/CoValueCoreSubscription.d.ts +14 -0
  29. package/dist/subscribe/CoValueCoreSubscription.d.ts.map +1 -0
  30. package/dist/subscribe/JazzError.d.ts +16 -0
  31. package/dist/subscribe/JazzError.d.ts.map +1 -0
  32. package/dist/subscribe/SubscriptionScope.d.ts +42 -0
  33. package/dist/subscribe/SubscriptionScope.d.ts.map +1 -0
  34. package/dist/subscribe/index.d.ts +21 -0
  35. package/dist/subscribe/index.d.ts.map +1 -0
  36. package/dist/subscribe/types.d.ts +12 -0
  37. package/dist/subscribe/types.d.ts.map +1 -0
  38. package/dist/subscribe/utils.d.ts +10 -0
  39. package/dist/subscribe/utils.d.ts.map +1 -0
  40. package/dist/testing.js +2 -2
  41. package/dist/testing.js.map +1 -1
  42. package/dist/tests/coMap.record.test.d.ts +2 -0
  43. package/dist/tests/coMap.record.test.d.ts.map +1 -0
  44. package/dist/tests/utils.d.ts +2 -2
  45. package/dist/tests/utils.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/coValues/account.ts +43 -31
  48. package/src/coValues/coFeed.ts +11 -7
  49. package/src/coValues/coList.ts +13 -17
  50. package/src/coValues/coMap.ts +72 -80
  51. package/src/coValues/coPlainText.ts +13 -2
  52. package/src/coValues/deepLoading.ts +4 -277
  53. package/src/coValues/extensions/imageDef.ts +1 -7
  54. package/src/coValues/group.ts +7 -6
  55. package/src/coValues/inbox.ts +4 -11
  56. package/src/coValues/interfaces.ts +54 -111
  57. package/src/implementation/createContext.ts +3 -4
  58. package/src/implementation/invites.ts +2 -2
  59. package/src/implementation/refs.ts +30 -121
  60. package/src/internal.ts +1 -2
  61. package/src/subscribe/CoValueCoreSubscription.ts +71 -0
  62. package/src/subscribe/JazzError.ts +48 -0
  63. package/src/subscribe/SubscriptionScope.ts +523 -0
  64. package/src/subscribe/index.ts +82 -0
  65. package/src/subscribe/types.ts +7 -0
  66. package/src/subscribe/utils.ts +36 -0
  67. package/src/testing.ts +1 -1
  68. package/src/tests/ContextManager.test.ts +13 -9
  69. package/src/tests/coFeed.test.ts +6 -6
  70. package/src/tests/coList.test.ts +304 -115
  71. package/src/tests/coMap.record.test.ts +325 -0
  72. package/src/tests/coMap.test.ts +718 -645
  73. package/src/tests/coPlainText.test.ts +2 -2
  74. package/src/tests/createContext.test.ts +8 -8
  75. package/src/tests/deepLoading.test.ts +8 -34
  76. package/src/tests/groupsAndAccounts.test.ts +6 -4
  77. package/src/tests/subscribe.test.ts +357 -42
  78. package/src/tests/utils.ts +8 -6
  79. package/dist/chunk-PYBQOYML.js.map +0 -1
  80. package/dist/implementation/subscriptionScope.d.ts +0 -34
  81. package/dist/implementation/subscriptionScope.d.ts.map +0 -1
  82. package/src/implementation/subscriptionScope.ts +0 -165
@@ -113,7 +113,7 @@ describe("ContextManager", () => {
113
113
 
114
114
  const credentials = {
115
115
  accountID: account.id,
116
- accountSecret: account._raw.core.node.account.agentSecret,
116
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
117
117
  provider: "test",
118
118
  };
119
119
 
@@ -130,7 +130,7 @@ describe("ContextManager", () => {
130
130
 
131
131
  const credentials = {
132
132
  accountID: account.id,
133
- accountSecret: account._raw.core.node.account.agentSecret,
133
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
134
134
  provider: "test",
135
135
  };
136
136
 
@@ -193,7 +193,7 @@ describe("ContextManager", () => {
193
193
  // Authenticate with credentials
194
194
  await manager.authenticate({
195
195
  accountID: account.id,
196
- accountSecret: account._raw.core.node.account.agentSecret,
196
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
197
197
  provider: "test",
198
198
  });
199
199
 
@@ -207,7 +207,7 @@ describe("ContextManager", () => {
207
207
 
208
208
  await manager.getAuthSecretStorage().set({
209
209
  accountID: account.id,
210
- accountSecret: account._raw.core.node.account.agentSecret,
210
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
211
211
  provider: "test",
212
212
  });
213
213
 
@@ -217,7 +217,7 @@ describe("ContextManager", () => {
217
217
  // Authenticate with same credentials
218
218
  await manager.authenticate({
219
219
  accountID: account.id,
220
- accountSecret: account._raw.core.node.account.agentSecret,
220
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
221
221
  provider: "test",
222
222
  });
223
223
 
@@ -253,9 +253,11 @@ describe("ContextManager", () => {
253
253
  customManager.getCurrentValue() as JazzAuthContext<CustomAccount>
254
254
  ).me;
255
255
 
256
+ console.log("before", account._refs.root?.id);
257
+
256
258
  await customManager.authenticate({
257
259
  accountID: account.id,
258
- accountSecret: account._raw.core.node.account.agentSecret,
260
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
259
261
  provider: "test",
260
262
  });
261
263
 
@@ -263,6 +265,8 @@ describe("ContextManager", () => {
263
265
  resolve: { root: true },
264
266
  });
265
267
 
268
+ console.log("after", me._refs.root?.id);
269
+
266
270
  expect(me.root.id).toBe(lastRootId);
267
271
  });
268
272
 
@@ -299,7 +303,7 @@ describe("ContextManager", () => {
299
303
 
300
304
  await customManager.authenticate({
301
305
  accountID: account.id,
302
- accountSecret: account._raw.core.node.account.agentSecret,
306
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
303
307
  provider: "test",
304
308
  });
305
309
 
@@ -365,7 +369,7 @@ describe("ContextManager", () => {
365
369
 
366
370
  await customManager.authenticate({
367
371
  accountID: account.id,
368
- accountSecret: account._raw.core.node.account.agentSecret,
372
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
369
373
  provider: "test",
370
374
  });
371
375
 
@@ -408,7 +412,7 @@ describe("ContextManager", () => {
408
412
 
409
413
  await manager.getAuthSecretStorage().set({
410
414
  accountID: account.id,
411
- accountSecret: account._raw.core.node.account.agentSecret,
415
+ accountSecret: account._raw.core.node.getCurrentAgent().agentSecret,
412
416
  provider: "test",
413
417
  });
414
418
 
@@ -117,7 +117,7 @@ describe("CoFeed resolution", async () => {
117
117
  await createJazzContextFromExistingCredentials({
118
118
  credentials: {
119
119
  accountID: me.id,
120
- secret: me._raw.agentSecret,
120
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
121
121
  },
122
122
  sessionProvider: randomSessionProvider,
123
123
  peersToLoadFrom: [initialAsPeer],
@@ -203,7 +203,7 @@ describe("CoFeed resolution", async () => {
203
203
  await createJazzContextFromExistingCredentials({
204
204
  credentials: {
205
205
  accountID: me.id,
206
- secret: me._raw.agentSecret,
206
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
207
207
  },
208
208
  sessionProvider: randomSessionProvider,
209
209
  peersToLoadFrom: [initialAsPeer],
@@ -273,7 +273,7 @@ describe("CoFeed resolution", async () => {
273
273
  await createJazzContextFromExistingCredentials({
274
274
  credentials: {
275
275
  accountID: me.id,
276
- secret: me._raw.agentSecret,
276
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
277
277
  },
278
278
  sessionProvider: randomSessionProvider,
279
279
  peersToLoadFrom: [initialAsPeer],
@@ -371,7 +371,7 @@ describe("FileStream loading & Subscription", async () => {
371
371
  await createJazzContextFromExistingCredentials({
372
372
  credentials: {
373
373
  accountID: me.id,
374
- secret: me._raw.agentSecret,
374
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
375
375
  },
376
376
  sessionProvider: randomSessionProvider,
377
377
  peersToLoadFrom: [initialAsPeer],
@@ -405,7 +405,7 @@ describe("FileStream loading & Subscription", async () => {
405
405
  await createJazzContextFromExistingCredentials({
406
406
  credentials: {
407
407
  accountID: me.id,
408
- secret: me._raw.agentSecret,
408
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
409
409
  },
410
410
  sessionProvider: randomSessionProvider,
411
411
  peersToLoadFrom: [initialAsPeer],
@@ -485,7 +485,7 @@ describe("FileStream loading & Subscription", async () => {
485
485
  await createJazzContextFromExistingCredentials({
486
486
  credentials: {
487
487
  accountID: me.id,
488
- secret: me._raw.agentSecret,
488
+ secret: me._raw.core.node.getCurrentAgent().agentSecret,
489
489
  },
490
490
  sessionProvider: randomSessionProvider,
491
491
  peersToLoadFrom: [initialAsPeer],
@@ -1,27 +1,40 @@
1
1
  import { cojsonInternals } from "cojson";
2
2
  import { WasmCrypto } from "cojson/crypto/WasmCrypto";
3
- import { describe, expect, test } from "vitest";
3
+ import { beforeEach, describe, expect, test, vi } from "vitest";
4
4
  import {
5
5
  Account,
6
6
  CoList,
7
7
  CoMap,
8
8
  Group,
9
+ Resolved,
9
10
  co,
10
11
  createJazzContextFromExistingCredentials,
11
12
  isControlledAccount,
13
+ subscribeToCoValue,
12
14
  } from "../index.js";
13
15
  import { randomSessionProvider } from "../internal.js";
16
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
17
+ import { waitFor } from "./utils.js";
14
18
 
15
19
  const connectedPeers = cojsonInternals.connectedPeers;
16
20
 
17
21
  const Crypto = await WasmCrypto.create();
18
22
 
19
- describe("Simple CoList operations", async () => {
20
- const me = await Account.create({
23
+ let me = await Account.create({
24
+ creationProps: { name: "Hermes Puggington" },
25
+ crypto: Crypto,
26
+ });
27
+
28
+ beforeEach(async () => {
29
+ await setupJazzTestSync();
30
+
31
+ me = await createJazzTestAccount({
32
+ isCurrentActiveAccount: true,
21
33
  creationProps: { name: "Hermes Puggington" },
22
- crypto: Crypto,
23
34
  });
35
+ });
24
36
 
37
+ describe("Simple CoList operations", async () => {
25
38
  class TestList extends CoList.Of(co.string) {}
26
39
 
27
40
  const list = TestList.create(["bread", "butter", "onion"], { owner: me });
@@ -347,11 +360,6 @@ describe("Simple CoList operations", async () => {
347
360
  });
348
361
 
349
362
  describe("CoList applyDiff operations", async () => {
350
- const me = await Account.create({
351
- creationProps: { name: "Hermes Puggington" },
352
- crypto: Crypto,
353
- });
354
-
355
363
  test("applyDiff with primitive values", () => {
356
364
  class StringList extends CoList.Of(co.string) {}
357
365
  const list = StringList.create(["a", "b", "c"], { owner: me });
@@ -485,147 +493,328 @@ describe("CoList resolution", async () => {
485
493
  expect(list[0]?.[0]?.id).toBeDefined();
486
494
  expect(list[1]?.[0]?.[0]).toBe("c");
487
495
  });
496
+ });
488
497
 
489
- test("Loading and availability", async () => {
490
- const { me, list } = await initNodeAndList();
491
-
492
- const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
493
- peer1role: "server",
494
- peer2role: "client",
495
- });
496
- if (!isControlledAccount(me)) {
497
- throw "me is not a controlled account";
498
+ describe("CoList subscription", async () => {
499
+ test("subscription on a locally available list with deep resolve", async () => {
500
+ class Item extends CoMap {
501
+ name = co.string;
498
502
  }
499
- me._raw.core.node.syncManager.addPeer(secondPeer);
500
- const { account: meOnSecondPeer } =
501
- await createJazzContextFromExistingCredentials({
502
- credentials: {
503
- accountID: me.id,
504
- secret: me._raw.agentSecret,
503
+
504
+ class TestList extends CoList.Of(co.ref(Item)) {}
505
+
506
+ const list = TestList.create(
507
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
508
+ { owner: me },
509
+ );
510
+
511
+ const updates: Resolved<TestList, { $each: true }>[] = [];
512
+ const spy = vi.fn((list) => updates.push(list));
513
+
514
+ TestList.subscribe(
515
+ list.id,
516
+ {
517
+ resolve: {
518
+ $each: true,
505
519
  },
506
- sessionProvider: randomSessionProvider,
507
- peersToLoadFrom: [initialAsPeer],
508
- crypto: Crypto,
509
- });
520
+ },
521
+ spy,
522
+ );
510
523
 
511
- const loadedList = await TestList.load(list.id, { loadAs: meOnSecondPeer });
524
+ expect(spy).not.toHaveBeenCalled();
512
525
 
513
- expect(loadedList?.[0]).toBe(null);
514
- expect(loadedList?._refs[0]?.id).toEqual(list[0]!.id);
526
+ await waitFor(() => expect(spy).toHaveBeenCalled());
515
527
 
516
- const loadedNestedList = await NestedList.load(list[0]!.id, {
517
- loadAs: meOnSecondPeer,
518
- });
528
+ expect(spy).toHaveBeenCalledTimes(1);
529
+
530
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
531
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
532
+
533
+ list[0]!.name = "Updated Item 1";
519
534
 
520
- expect(loadedList?.[0]).toBeDefined();
521
- expect(loadedList?.[0]?.[0]).toBe(null);
522
- expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]![0]!.id);
523
- // TODO: this should be ref equal
524
- // expect(loadedList?._refs[0]?.value).toEqual(loadedNestedList);
525
- expect(loadedList?._refs[0]?.value?.toJSON()).toEqual(
526
- loadedNestedList?.toJSON(),
535
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
536
+
537
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
538
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
539
+
540
+ expect(spy).toHaveBeenCalledTimes(2);
541
+ });
542
+
543
+ test("subscription on a locally available list with autoload", async () => {
544
+ class Item extends CoMap {
545
+ name = co.string;
546
+ }
547
+
548
+ class TestList extends CoList.Of(co.ref(Item)) {}
549
+
550
+ const list = TestList.create(
551
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
552
+ { owner: me },
527
553
  );
528
554
 
529
- const loadedTwiceNestedList = await TwiceNestedList.load(list[0]![0]!.id, {
530
- loadAs: meOnSecondPeer,
531
- });
555
+ const updates: TestList[] = [];
556
+ const spy = vi.fn((list) => updates.push(list));
557
+
558
+ TestList.subscribe(list.id, {}, spy);
559
+
560
+ expect(spy).not.toHaveBeenCalled();
561
+
562
+ await waitFor(() => expect(spy).toHaveBeenCalled());
563
+
564
+ expect(spy).toHaveBeenCalledTimes(1);
565
+
566
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
567
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
568
+
569
+ list[0]!.name = "Updated Item 1";
570
+
571
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
572
+
573
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
574
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
532
575
 
533
- expect(loadedList?.[0]?.[0]).toBeDefined();
534
- expect(loadedList?.[0]?.[0]?.[0]).toBe("a");
535
- expect(loadedList?.[0]?.[0]?.joined()).toBe("a,b");
536
- expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]?.[0]?.id);
537
- // TODO: this should be ref equal
538
- // expect(loadedList?.[0]?._refs[0]?.value).toEqual(loadedTwiceNestedList);
539
- expect(loadedList?.[0]?._refs[0]?.value?.toJSON()).toEqual(
540
- loadedTwiceNestedList?.toJSON(),
576
+ expect(spy).toHaveBeenCalledTimes(2);
577
+ });
578
+
579
+ test("subscription on a locally available list with syncResolution", async () => {
580
+ class Item extends CoMap {
581
+ name = co.string;
582
+ }
583
+
584
+ class TestList extends CoList.Of(co.ref(Item)) {}
585
+
586
+ const list = TestList.create(
587
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
588
+ { owner: me },
541
589
  );
542
590
 
543
- const otherNestedList = NestedList.create(
544
- [TwiceNestedList.create(["e", "f"], { owner: meOnSecondPeer })],
545
- { owner: meOnSecondPeer },
591
+ const updates: TestList[] = [];
592
+ const spy = vi.fn((list) => updates.push(list));
593
+
594
+ subscribeToCoValue(
595
+ TestList,
596
+ list.id,
597
+ {
598
+ syncResolution: true,
599
+ loadAs: Account.getMe(),
600
+ },
601
+ spy,
546
602
  );
547
603
 
548
- loadedList![0] = otherNestedList;
549
- // TODO: this should be ref equal
550
- // expect(loadedList?.[0]).toEqual(otherNestedList);
551
- expect(loadedList?._refs[0]?.value?.toJSON()).toEqual(
552
- otherNestedList.toJSON(),
604
+ expect(spy).toHaveBeenCalled();
605
+ expect(spy).toHaveBeenCalledTimes(1);
606
+
607
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
608
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
609
+
610
+ expect(spy).toHaveBeenCalledTimes(1);
611
+
612
+ list[0]!.name = "Updated Item 1";
613
+
614
+ expect(spy).toHaveBeenCalledTimes(2);
615
+
616
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
617
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
618
+
619
+ expect(spy).toHaveBeenCalledTimes(2);
620
+ });
621
+
622
+ test("subscription on a remotely available list with deep resolve", async () => {
623
+ class Item extends CoMap {
624
+ name = co.string;
625
+ }
626
+
627
+ class TestList extends CoList.Of(co.ref(Item)) {}
628
+
629
+ const group = Group.create();
630
+ group.addMember("everyone", "writer");
631
+
632
+ const list = TestList.create(
633
+ [
634
+ Item.create({ name: "Item 1" }, group),
635
+ Item.create({ name: "Item 2" }, group),
636
+ ],
637
+ group,
553
638
  );
554
- expect(loadedList?._refs[0]?.id).toEqual(otherNestedList.id);
639
+
640
+ const userB = await createJazzTestAccount();
641
+
642
+ const updates: Resolved<TestList, { $each: true }>[] = [];
643
+ const spy = vi.fn((list) => updates.push(list));
644
+
645
+ TestList.subscribe(
646
+ list.id,
647
+ {
648
+ resolve: {
649
+ $each: true,
650
+ },
651
+ loadAs: userB,
652
+ },
653
+ spy,
654
+ );
655
+
656
+ expect(spy).not.toHaveBeenCalled();
657
+
658
+ await waitFor(() => expect(spy).toHaveBeenCalled());
659
+
660
+ expect(spy).toHaveBeenCalledTimes(1);
661
+
662
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
663
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
664
+
665
+ list[0]!.name = "Updated Item 1";
666
+
667
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
668
+
669
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
670
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
671
+
672
+ expect(spy).toHaveBeenCalledTimes(2);
555
673
  });
556
674
 
557
- test("Subscription & auto-resolution", async () => {
558
- const { me, list } = await initNodeAndList();
675
+ test("subscription on a remotely available list with autoload", async () => {
676
+ class Item extends CoMap {
677
+ name = co.string;
678
+ }
559
679
 
560
- const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
561
- peer1role: "server",
562
- peer2role: "client",
563
- });
564
- if (!isControlledAccount(me)) {
565
- throw "me is not a controlled account";
680
+ class TestList extends CoList.Of(co.ref(Item)) {}
681
+
682
+ const group = Group.create();
683
+ group.addMember("everyone", "writer");
684
+
685
+ const list = TestList.create(
686
+ [
687
+ Item.create({ name: "Item 1" }, group),
688
+ Item.create({ name: "Item 2" }, group),
689
+ ],
690
+ group,
691
+ );
692
+
693
+ const updates: TestList[] = [];
694
+ const spy = vi.fn((list) => updates.push(list));
695
+
696
+ const userB = await createJazzTestAccount();
697
+
698
+ TestList.subscribe(
699
+ list.id,
700
+ {
701
+ loadAs: userB,
702
+ },
703
+ spy,
704
+ );
705
+
706
+ expect(spy).not.toHaveBeenCalled();
707
+
708
+ await waitFor(() => expect(spy).toHaveBeenCalled());
709
+
710
+ expect(spy).toHaveBeenCalledTimes(1);
711
+
712
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
713
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
714
+
715
+ list[0]!.name = "Updated Item 1";
716
+
717
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
718
+
719
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
720
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
721
+
722
+ expect(spy).toHaveBeenCalledTimes(2);
723
+ });
724
+
725
+ test("replacing list items triggers updates", async () => {
726
+ class Item extends CoMap {
727
+ name = co.string;
566
728
  }
567
- me._raw.core.node.syncManager.addPeer(secondPeer);
568
- const { account: meOnSecondPeer } =
569
- await createJazzContextFromExistingCredentials({
570
- credentials: {
571
- accountID: me.id,
572
- secret: me._raw.agentSecret,
573
- },
574
- sessionProvider: randomSessionProvider,
575
- peersToLoadFrom: [initialAsPeer],
576
- crypto: Crypto,
577
- });
578
729
 
579
- const queue = new cojsonInternals.Channel();
730
+ class TestList extends CoList.Of(co.ref(Item)) {}
731
+
732
+ const list = TestList.create(
733
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
734
+ { owner: me },
735
+ );
736
+
737
+ const updates: Resolved<TestList, { $each: true }>[] = [];
738
+ const spy = vi.fn((list) => updates.push(list));
580
739
 
581
740
  TestList.subscribe(
582
741
  list.id,
583
- { loadAs: meOnSecondPeer },
584
- (subscribedList) => {
585
- console.log(
586
- "subscribedList?.[0]?.[0]?.[0]",
587
- subscribedList?.[0]?.[0]?.[0],
588
- );
589
- void queue.push(subscribedList);
742
+ {
743
+ resolve: {
744
+ $each: true,
745
+ },
590
746
  },
747
+ spy,
591
748
  );
592
749
 
593
- const update1 = (await queue.next()).value;
594
- expect(update1?.[0]).toBe(null);
750
+ expect(spy).not.toHaveBeenCalled();
595
751
 
596
- const update2 = (await queue.next()).value;
597
- expect(update2?.[0]).toBeDefined();
598
- expect(update2?.[0]?.[0]).toBe(null);
752
+ await waitFor(() => expect(spy).toHaveBeenCalled());
599
753
 
600
- const update3 = (await queue.next()).value;
601
- expect(update3?.[0]?.[0]).toBeDefined();
602
- expect(update3?.[0]?.[0]?.[0]).toBe("a");
603
- expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
754
+ expect(spy).toHaveBeenCalledTimes(1);
604
755
 
605
- update3[0]![0]![0] = "x";
756
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
757
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
606
758
 
607
- const update4 = (await queue.next()).value;
608
- expect(update4?.[0]?.[0]?.[0]).toBe("x");
759
+ list[0] = Item.create({ name: "New Item 1" });
609
760
 
610
- // When assigning a new nested value, we get an update
761
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
611
762
 
612
- const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
613
- owner: meOnSecondPeer,
614
- });
763
+ expect(updates[1]?.[0]?.name).toEqual("New Item 1");
764
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
615
765
 
616
- const newNestedList = NestedList.create([newTwiceNestedList], {
617
- owner: meOnSecondPeer,
618
- });
766
+ expect(spy).toHaveBeenCalledTimes(2);
767
+ });
768
+
769
+ test("pushing a new item triggers updates correctly", async () => {
770
+ class Item extends CoMap {
771
+ name = co.string;
772
+ }
773
+
774
+ class TestList extends CoList.Of(co.ref(Item)) {}
775
+
776
+ const group = Group.create();
777
+ group.addMember("everyone", "writer");
778
+
779
+ const list = TestList.create(
780
+ [
781
+ Item.create({ name: "Item 1" }, group),
782
+ Item.create({ name: "Item 2" }, group),
783
+ ],
784
+ group,
785
+ );
786
+
787
+ const updates: TestList[] = [];
788
+ const spy = vi.fn((list) => updates.push(list));
789
+
790
+ const userB = await createJazzTestAccount();
791
+
792
+ TestList.subscribe(
793
+ list.id,
794
+ {
795
+ loadAs: userB,
796
+ resolve: {
797
+ $each: true,
798
+ },
799
+ },
800
+ (update) => {
801
+ spy(update);
802
+
803
+ // The update should be triggered only when the new item is loaded
804
+ for (const item of update) {
805
+ expect(item).toBeDefined();
806
+ }
807
+ },
808
+ );
809
+
810
+ await waitFor(() => expect(spy).toHaveBeenCalled());
811
+
812
+ expect(spy).toHaveBeenCalledTimes(1);
619
813
 
620
- update4[0] = newNestedList;
814
+ list.push(Item.create({ name: "Item 3" }, group));
621
815
 
622
- const update5 = (await queue.next()).value;
623
- expect(update5?.[0]?.[0]?.[0]).toBe("y");
624
- expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
816
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
625
817
 
626
- // we get updates when the new nested value changes
627
- newTwiceNestedList[0] = "w";
628
- const update6 = (await queue.next()).value;
629
- expect(update6?.[0]?.[0]?.[0]).toBe("w");
818
+ expect(spy).toHaveBeenCalledTimes(2);
630
819
  });
631
820
  });