@loro-extended/change 5.1.0 → 5.3.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/README.md +94 -0
- package/dist/index.d.ts +283 -137
- package/dist/index.js +2027 -1930
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/fork-at.test.ts +260 -0
- package/src/functional-helpers.test.ts +527 -0
- package/src/functional-helpers.ts +172 -12
- package/src/index.ts +7 -2
- package/src/loro.ts +26 -2
- package/src/shape.ts +30 -5
- package/src/typed-doc.ts +58 -6
- package/src/typed-refs/base.ts +18 -0
- package/src/typed-refs/doc-ref-internals.ts +2 -2
- package/src/typed-refs/list-ref-base-internals.ts +2 -2
- package/src/typed-refs/list-ref-base.ts +2 -2
- package/src/typed-refs/record-ref-internals.ts +2 -2
- package/src/typed-refs/struct-ref-internals.ts +2 -2
- package/src/typed-refs/struct-ref.ts +7 -1
- package/src/typed-refs/tree-deleted-nodes.test.ts +213 -0
- package/src/typed-refs/tree-loro.test.ts +52 -0
- package/src/typed-refs/tree-node-ref-internals.ts +34 -1
- package/src/typed-refs/tree-node-ref.test.ts +24 -17
- package/src/typed-refs/tree-node-ref.ts +8 -0
- package/src/typed-refs/tree-node.test.ts +54 -22
- package/src/typed-refs/tree-ref.ts +10 -3
|
@@ -361,6 +361,131 @@ describe("functional helpers", () => {
|
|
|
361
361
|
})
|
|
362
362
|
})
|
|
363
363
|
|
|
364
|
+
describe("loro(ref).subscribe() for imported (remote) changes", () => {
|
|
365
|
+
it("should fire TextRef subscription when changes are imported", () => {
|
|
366
|
+
// Create two documents - simulating two clients
|
|
367
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
368
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
369
|
+
|
|
370
|
+
// Set up subscription on doc2's title ref
|
|
371
|
+
const callback = vi.fn()
|
|
372
|
+
const unsubscribe = loro(doc2.title).subscribe(callback)
|
|
373
|
+
|
|
374
|
+
// Make changes on doc1
|
|
375
|
+
doc1.title.insert(0, "Hello from doc1")
|
|
376
|
+
loro(doc1).doc.commit()
|
|
377
|
+
|
|
378
|
+
// Export from doc1 and import into doc2 (simulating sync)
|
|
379
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
380
|
+
loro(doc2).doc.import(snapshot)
|
|
381
|
+
|
|
382
|
+
// The subscription should have fired
|
|
383
|
+
expect(callback).toHaveBeenCalled()
|
|
384
|
+
|
|
385
|
+
// And the value should be updated
|
|
386
|
+
expect(doc2.title.toString()).toBe("Hello from doc1")
|
|
387
|
+
|
|
388
|
+
unsubscribe()
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it("should fire CounterRef subscription when changes are imported", () => {
|
|
392
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
393
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
394
|
+
|
|
395
|
+
const callback = vi.fn()
|
|
396
|
+
const unsubscribe = loro(doc2.count).subscribe(callback)
|
|
397
|
+
|
|
398
|
+
doc1.count.increment(42)
|
|
399
|
+
loro(doc1).doc.commit()
|
|
400
|
+
|
|
401
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
402
|
+
loro(doc2).doc.import(snapshot)
|
|
403
|
+
|
|
404
|
+
expect(callback).toHaveBeenCalled()
|
|
405
|
+
expect(doc2.count.value).toBe(42)
|
|
406
|
+
|
|
407
|
+
unsubscribe()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it("should fire ListRef subscription when changes are imported", () => {
|
|
411
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
412
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
413
|
+
|
|
414
|
+
const callback = vi.fn()
|
|
415
|
+
const unsubscribe = loro(doc2.items).subscribe(callback)
|
|
416
|
+
|
|
417
|
+
doc1.items.push("item1")
|
|
418
|
+
doc1.items.push("item2")
|
|
419
|
+
loro(doc1).doc.commit()
|
|
420
|
+
|
|
421
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
422
|
+
loro(doc2).doc.import(snapshot)
|
|
423
|
+
|
|
424
|
+
expect(callback).toHaveBeenCalled()
|
|
425
|
+
expect(doc2.items.toJSON()).toEqual(["item1", "item2"])
|
|
426
|
+
|
|
427
|
+
unsubscribe()
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it("should fire doc-level subscription when changes are imported", () => {
|
|
431
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
432
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
433
|
+
|
|
434
|
+
const callback = vi.fn()
|
|
435
|
+
const unsubscribe = loro(doc2).doc.subscribe(callback)
|
|
436
|
+
|
|
437
|
+
doc1.title.insert(0, "Hello")
|
|
438
|
+
loro(doc1).doc.commit()
|
|
439
|
+
|
|
440
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
441
|
+
loro(doc2).doc.import(snapshot)
|
|
442
|
+
|
|
443
|
+
expect(callback).toHaveBeenCalled()
|
|
444
|
+
|
|
445
|
+
unsubscribe()
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it("should NOT fire subscription for containers that were not changed", () => {
|
|
449
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
450
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
451
|
+
|
|
452
|
+
// Subscribe to count, but only change title
|
|
453
|
+
const countCallback = vi.fn()
|
|
454
|
+
const unsubscribe = loro(doc2.count).subscribe(countCallback)
|
|
455
|
+
|
|
456
|
+
doc1.title.insert(0, "Hello")
|
|
457
|
+
loro(doc1).doc.commit()
|
|
458
|
+
|
|
459
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
460
|
+
loro(doc2).doc.import(snapshot)
|
|
461
|
+
|
|
462
|
+
// Count subscription should NOT have fired since count wasn't changed
|
|
463
|
+
expect(countCallback).not.toHaveBeenCalled()
|
|
464
|
+
|
|
465
|
+
unsubscribe()
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it("should provide updated value in subscription callback", () => {
|
|
469
|
+
const doc1 = createTypedDoc(fullSchema)
|
|
470
|
+
const doc2 = createTypedDoc(fullSchema)
|
|
471
|
+
|
|
472
|
+
let capturedValue: string | undefined
|
|
473
|
+
const unsubscribe = loro(doc2.title).subscribe(() => {
|
|
474
|
+
capturedValue = doc2.title.toString()
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
doc1.title.insert(0, "Remote text")
|
|
478
|
+
loro(doc1).doc.commit()
|
|
479
|
+
|
|
480
|
+
const snapshot = loro(doc1).doc.export({ mode: "snapshot" })
|
|
481
|
+
loro(doc2).doc.import(snapshot)
|
|
482
|
+
|
|
483
|
+
expect(capturedValue).toBe("Remote text")
|
|
484
|
+
|
|
485
|
+
unsubscribe()
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
|
+
|
|
364
489
|
describe("getLoroDoc() on refs", () => {
|
|
365
490
|
it("should return LoroDoc from TextRef", () => {
|
|
366
491
|
const doc = createTypedDoc(fullSchema)
|
|
@@ -458,4 +583,406 @@ describe("functional helpers", () => {
|
|
|
458
583
|
expect(getLoroContainer(doc.tree)).toBe(loro(doc.tree).container)
|
|
459
584
|
})
|
|
460
585
|
})
|
|
586
|
+
|
|
587
|
+
describe("change() on refs", () => {
|
|
588
|
+
describe("ListRef", () => {
|
|
589
|
+
it("should batch push operations", () => {
|
|
590
|
+
const doc = createTypedDoc(fullSchema)
|
|
591
|
+
|
|
592
|
+
change(doc.items, draft => {
|
|
593
|
+
draft.push("item1")
|
|
594
|
+
draft.push("item2")
|
|
595
|
+
draft.push("item3")
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
expect(doc.items.toJSON()).toEqual(["item1", "item2", "item3"])
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it("should batch delete and push operations", () => {
|
|
602
|
+
const doc = createTypedDoc(fullSchema)
|
|
603
|
+
|
|
604
|
+
// Setup initial data
|
|
605
|
+
doc.items.push("a")
|
|
606
|
+
doc.items.push("b")
|
|
607
|
+
doc.items.push("c")
|
|
608
|
+
|
|
609
|
+
change(doc.items, draft => {
|
|
610
|
+
draft.delete(1, 1) // Remove "b"
|
|
611
|
+
draft.push("d")
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
expect(doc.items.toJSON()).toEqual(["a", "c", "d"])
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it("should return the original ref for chaining", () => {
|
|
618
|
+
const doc = createTypedDoc(fullSchema)
|
|
619
|
+
|
|
620
|
+
const result = change(doc.items, draft => {
|
|
621
|
+
draft.push("item1")
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
expect(result).toBe(doc.items)
|
|
625
|
+
result.push("item2")
|
|
626
|
+
expect(doc.items.toJSON()).toEqual(["item1", "item2"])
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it("should support find-and-mutate patterns with value shapes", () => {
|
|
630
|
+
const listSchema = Shape.doc({
|
|
631
|
+
items: Shape.list(
|
|
632
|
+
Shape.plain.struct({
|
|
633
|
+
id: Shape.plain.string(),
|
|
634
|
+
count: Shape.plain.number(),
|
|
635
|
+
}),
|
|
636
|
+
),
|
|
637
|
+
})
|
|
638
|
+
const doc = createTypedDoc(listSchema)
|
|
639
|
+
|
|
640
|
+
// Setup initial data
|
|
641
|
+
doc.items.push({ id: "a", count: 0 })
|
|
642
|
+
doc.items.push({ id: "b", count: 0 })
|
|
643
|
+
|
|
644
|
+
change(doc.items, draft => {
|
|
645
|
+
const item = draft.find(i => i.id === "b")
|
|
646
|
+
if (item) {
|
|
647
|
+
item.count = 10
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
expect(doc.items.toJSON()).toEqual([
|
|
652
|
+
{ id: "a", count: 0 },
|
|
653
|
+
{ id: "b", count: 10 },
|
|
654
|
+
])
|
|
655
|
+
})
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
describe("TextRef", () => {
|
|
659
|
+
it("should batch insert operations", () => {
|
|
660
|
+
const doc = createTypedDoc(fullSchema)
|
|
661
|
+
|
|
662
|
+
change(doc.title, draft => {
|
|
663
|
+
draft.insert(0, "Hello")
|
|
664
|
+
draft.insert(5, " World")
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
expect(doc.title.toString()).toBe("Hello World")
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
it("should batch insert and delete operations", () => {
|
|
671
|
+
const doc = createTypedDoc(fullSchema)
|
|
672
|
+
|
|
673
|
+
doc.title.insert(0, "Hello World")
|
|
674
|
+
|
|
675
|
+
change(doc.title, draft => {
|
|
676
|
+
draft.delete(5, 6) // Remove " World"
|
|
677
|
+
draft.insert(5, " Universe")
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
expect(doc.title.toString()).toBe("Hello Universe")
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
it("should support update operation", () => {
|
|
684
|
+
const doc = createTypedDoc(fullSchema)
|
|
685
|
+
|
|
686
|
+
doc.title.insert(0, "Old Text")
|
|
687
|
+
|
|
688
|
+
change(doc.title, draft => {
|
|
689
|
+
draft.update("New Text")
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
expect(doc.title.toString()).toBe("New Text")
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
it("should return the original ref for chaining", () => {
|
|
696
|
+
const doc = createTypedDoc(fullSchema)
|
|
697
|
+
|
|
698
|
+
const result = change(doc.title, draft => {
|
|
699
|
+
draft.insert(0, "Hello")
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
expect(result).toBe(doc.title)
|
|
703
|
+
result.insert(5, "!")
|
|
704
|
+
expect(doc.title.toString()).toBe("Hello!")
|
|
705
|
+
})
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
describe("CounterRef", () => {
|
|
709
|
+
it("should batch increment operations", () => {
|
|
710
|
+
const doc = createTypedDoc(fullSchema)
|
|
711
|
+
|
|
712
|
+
change(doc.count, draft => {
|
|
713
|
+
draft.increment(5)
|
|
714
|
+
draft.increment(3)
|
|
715
|
+
draft.increment(2)
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
expect(doc.count.value).toBe(10)
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it("should batch increment and decrement operations", () => {
|
|
722
|
+
const doc = createTypedDoc(fullSchema)
|
|
723
|
+
|
|
724
|
+
doc.count.increment(10)
|
|
725
|
+
|
|
726
|
+
change(doc.count, draft => {
|
|
727
|
+
draft.increment(5)
|
|
728
|
+
draft.decrement(3)
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
expect(doc.count.value).toBe(12)
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
it("should return the original ref for chaining", () => {
|
|
735
|
+
const doc = createTypedDoc(fullSchema)
|
|
736
|
+
|
|
737
|
+
const result = change(doc.count, draft => {
|
|
738
|
+
draft.increment(5)
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
expect(result).toBe(doc.count)
|
|
742
|
+
result.increment(3)
|
|
743
|
+
expect(doc.count.value).toBe(8)
|
|
744
|
+
})
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
describe("StructRef", () => {
|
|
748
|
+
it("should batch property assignments", () => {
|
|
749
|
+
const doc = createTypedDoc(fullSchema)
|
|
750
|
+
|
|
751
|
+
change(doc.profile, draft => {
|
|
752
|
+
draft.bio.insert(0, "Hello")
|
|
753
|
+
draft.age.increment(25)
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
expect(doc.profile.bio.toString()).toBe("Hello")
|
|
757
|
+
expect(doc.profile.age.value).toBe(25)
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
it("should return the original ref for chaining", () => {
|
|
761
|
+
const doc = createTypedDoc(fullSchema)
|
|
762
|
+
|
|
763
|
+
const result = change(doc.profile, draft => {
|
|
764
|
+
draft.bio.insert(0, "Test")
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
expect(result).toBe(doc.profile)
|
|
768
|
+
})
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
describe("RecordRef", () => {
|
|
772
|
+
it("should batch set operations", () => {
|
|
773
|
+
const doc = createTypedDoc(fullSchema)
|
|
774
|
+
|
|
775
|
+
change(doc.users, draft => {
|
|
776
|
+
draft.set("alice", { name: "Alice" })
|
|
777
|
+
draft.set("bob", { name: "Bob" })
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
expect(doc.users.toJSON()).toEqual({
|
|
781
|
+
alice: { name: "Alice" },
|
|
782
|
+
bob: { name: "Bob" },
|
|
783
|
+
})
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
it("should batch set and delete operations", () => {
|
|
787
|
+
const doc = createTypedDoc(fullSchema)
|
|
788
|
+
|
|
789
|
+
doc.users.set("alice", { name: "Alice" })
|
|
790
|
+
doc.users.set("bob", { name: "Bob" })
|
|
791
|
+
|
|
792
|
+
change(doc.users, draft => {
|
|
793
|
+
draft.delete("alice")
|
|
794
|
+
draft.set("charlie", { name: "Charlie" })
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
expect(doc.users.toJSON()).toEqual({
|
|
798
|
+
bob: { name: "Bob" },
|
|
799
|
+
charlie: { name: "Charlie" },
|
|
800
|
+
})
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
it("should return the original ref for chaining", () => {
|
|
804
|
+
const doc = createTypedDoc(fullSchema)
|
|
805
|
+
|
|
806
|
+
const result = change(doc.users, draft => {
|
|
807
|
+
draft.set("alice", { name: "Alice" })
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
expect(result).toBe(doc.users)
|
|
811
|
+
})
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
describe("TreeRef", () => {
|
|
815
|
+
it("should batch createNode operations", () => {
|
|
816
|
+
const doc = createTypedDoc(fullSchema)
|
|
817
|
+
|
|
818
|
+
change(doc.tree, draft => {
|
|
819
|
+
draft.createNode()
|
|
820
|
+
draft.createNode()
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
expect(doc.tree.roots().length).toBe(2)
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
it("should batch node creation with initial data", () => {
|
|
827
|
+
const doc = createTypedDoc(fullSchema)
|
|
828
|
+
|
|
829
|
+
change(doc.tree, draft => {
|
|
830
|
+
const node1 = draft.createNode()
|
|
831
|
+
node1.data.name.insert(0, "Node 1")
|
|
832
|
+
|
|
833
|
+
const node2 = draft.createNode()
|
|
834
|
+
node2.data.name.insert(0, "Node 2")
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const roots = doc.tree.roots()
|
|
838
|
+
expect(roots.length).toBe(2)
|
|
839
|
+
expect(roots[0].data.name.toString()).toBe("Node 1")
|
|
840
|
+
expect(roots[1].data.name.toString()).toBe("Node 2")
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
it("should return the original ref for chaining", () => {
|
|
844
|
+
const doc = createTypedDoc(fullSchema)
|
|
845
|
+
|
|
846
|
+
const result = change(doc.tree, draft => {
|
|
847
|
+
draft.createNode()
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
expect(result).toBe(doc.tree)
|
|
851
|
+
})
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
describe("MovableListRef", () => {
|
|
855
|
+
it("should batch push operations", () => {
|
|
856
|
+
const doc = createTypedDoc(fullSchema)
|
|
857
|
+
|
|
858
|
+
change(doc.movableItems, draft => {
|
|
859
|
+
draft.push("item1")
|
|
860
|
+
draft.push("item2")
|
|
861
|
+
})
|
|
862
|
+
|
|
863
|
+
expect(doc.movableItems.toJSON()).toEqual(["item1", "item2"])
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
it("should return the original ref for chaining", () => {
|
|
867
|
+
const doc = createTypedDoc(fullSchema)
|
|
868
|
+
|
|
869
|
+
const result = change(doc.movableItems, draft => {
|
|
870
|
+
draft.push("item1")
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
expect(result).toBe(doc.movableItems)
|
|
874
|
+
})
|
|
875
|
+
})
|
|
876
|
+
|
|
877
|
+
describe("nested change() calls", () => {
|
|
878
|
+
it("should handle nested change() calls correctly", () => {
|
|
879
|
+
const doc = createTypedDoc(fullSchema)
|
|
880
|
+
|
|
881
|
+
change(doc.items, outerDraft => {
|
|
882
|
+
outerDraft.push("outer1")
|
|
883
|
+
|
|
884
|
+
// Nested change on a different ref
|
|
885
|
+
change(doc.count, innerDraft => {
|
|
886
|
+
innerDraft.increment(10)
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
outerDraft.push("outer2")
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
expect(doc.items.toJSON()).toEqual(["outer1", "outer2"])
|
|
893
|
+
expect(doc.count.value).toBe(10)
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
it("should handle deeply nested change() calls", () => {
|
|
897
|
+
const doc = createTypedDoc(fullSchema)
|
|
898
|
+
|
|
899
|
+
change(doc.items, d1 => {
|
|
900
|
+
d1.push("L1")
|
|
901
|
+
|
|
902
|
+
change(doc.count, d2 => {
|
|
903
|
+
d2.increment(1)
|
|
904
|
+
|
|
905
|
+
change(doc.title, d3 => {
|
|
906
|
+
d3.insert(0, "Deep")
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
d2.increment(2)
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
d1.push("L1-end")
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
expect(doc.items.toJSON()).toEqual(["L1", "L1-end"])
|
|
916
|
+
expect(doc.count.value).toBe(3)
|
|
917
|
+
expect(doc.title.toString()).toBe("Deep")
|
|
918
|
+
})
|
|
919
|
+
})
|
|
920
|
+
|
|
921
|
+
describe("encapsulation use case", () => {
|
|
922
|
+
it("should allow passing refs without exposing the doc", () => {
|
|
923
|
+
const doc = createTypedDoc(fullSchema)
|
|
924
|
+
|
|
925
|
+
// Simulate a library function that only receives the ref
|
|
926
|
+
function addItems(itemsRef: typeof doc.items) {
|
|
927
|
+
change(itemsRef, draft => {
|
|
928
|
+
draft.push("library-item-1")
|
|
929
|
+
draft.push("library-item-2")
|
|
930
|
+
})
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// User code passes the ref, not the doc
|
|
934
|
+
addItems(doc.items)
|
|
935
|
+
|
|
936
|
+
expect(doc.items.toJSON()).toEqual(["library-item-1", "library-item-2"])
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
it("should allow passing TreeRef for state machine use case", () => {
|
|
940
|
+
const doc = createTypedDoc(fullSchema)
|
|
941
|
+
|
|
942
|
+
// Simulate a state machine library
|
|
943
|
+
function addStates(statesRef: typeof doc.tree) {
|
|
944
|
+
change(statesRef, draft => {
|
|
945
|
+
const idle = draft.createNode()
|
|
946
|
+
idle.data.name.insert(0, "idle")
|
|
947
|
+
|
|
948
|
+
const running = draft.createNode()
|
|
949
|
+
running.data.name.insert(0, "running")
|
|
950
|
+
})
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
addStates(doc.tree)
|
|
954
|
+
|
|
955
|
+
const roots = doc.tree.roots()
|
|
956
|
+
expect(roots.length).toBe(2)
|
|
957
|
+
expect(roots[0].data.name.toString()).toBe("idle")
|
|
958
|
+
expect(roots[1].data.name.toString()).toBe("running")
|
|
959
|
+
})
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
describe("regression: doc.change() still works", () => {
|
|
963
|
+
it("should still support doc.change() method", () => {
|
|
964
|
+
const doc = createTypedDoc(fullSchema)
|
|
965
|
+
|
|
966
|
+
doc.change(draft => {
|
|
967
|
+
draft.title.insert(0, "Hello")
|
|
968
|
+
draft.count.increment(5)
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
expect(doc.title.toString()).toBe("Hello")
|
|
972
|
+
expect(doc.count.value).toBe(5)
|
|
973
|
+
})
|
|
974
|
+
|
|
975
|
+
it("should still support change(doc, fn) helper", () => {
|
|
976
|
+
const doc = createTypedDoc(fullSchema)
|
|
977
|
+
|
|
978
|
+
change(doc, draft => {
|
|
979
|
+
draft.title.insert(0, "World")
|
|
980
|
+
draft.count.increment(10)
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
expect(doc.title.toString()).toBe("World")
|
|
984
|
+
expect(doc.count.value).toBe(10)
|
|
985
|
+
})
|
|
986
|
+
})
|
|
987
|
+
})
|
|
461
988
|
})
|