Skip to content

Commit 8338c05

Browse files
committed
docs: update examples on list and map
1 parent db0b0d5 commit 8338c05

File tree

3 files changed

+220
-24
lines changed

3 files changed

+220
-24
lines changed

07_list.test.ts

Lines changed: 155 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,164 @@
1-
import { Loro, LoroList, LoroMovableList, LoroText } from "npm:[email protected]";
1+
import { Cursor, Loro } from "npm:[email protected]";
22
import { expect } from "npm:[email protected]";
33

44
Deno.test("List", () => {
5-
let list = new LoroList();
6-
list.push(0);
7-
list.push("1");
8-
const doc = new Loro();
9-
const map = doc.getMap("root");
10-
list = map.setContainer("list", list);
11-
expect(doc.toJson()).toStrictEqual({ root: { list: [0, "1"] } });
12-
list.delete(0, 1);
13-
expect(doc.toJson()).toStrictEqual({ root: { list: ["1"] } });
5+
const docA = new Loro();
6+
docA.setPeerId("1");
7+
const listA = docA.getList("list");
8+
listA.push(0);
9+
listA.push(1);
10+
listA.push(2);
11+
const bytes: Uint8Array = docA.exportSnapshot();
12+
13+
const docB = Loro.fromSnapshot(bytes);
14+
docB.setPeerId("2");
15+
const listB = docB.getList("list");
16+
{
17+
// Concurrently docA and docB update element at index 2
18+
// docA updates it to 8
19+
// docB updates it to 9
20+
// docA.toJson() should return { list: [0, 1, 8] }
21+
// docB.toJson() should return { list: [0, 1, 9] }
22+
23+
listB.delete(2, 1);
24+
listB.insert(2, 9);
25+
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });
26+
listA.delete(2, 1);
27+
listA.insert(2, 8);
28+
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8] });
29+
}
30+
31+
{
32+
// Merge docA and docB
33+
docA.import(docB.exportFrom(docA.version()));
34+
docB.import(docA.exportFrom(docB.version()));
35+
}
36+
37+
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8, 9] });
38+
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 8, 9] });
1439
})
1540

1641
Deno.test("MovableList", () => {
17-
let list = new LoroMovableList();
18-
list.push(0);
19-
list.push("1");
42+
const docA = new Loro();
43+
docA.setPeerId("1");
44+
const listA = docA.getMovableList("list");
45+
listA.push(0);
46+
listA.push(1);
47+
listA.push(2);
48+
const bytes: Uint8Array = docA.exportSnapshot();
49+
50+
const docB = Loro.fromSnapshot(bytes);
51+
docB.setPeerId("2");
52+
const listB = docB.getMovableList("list");
53+
{
54+
// Concurrently docA and docB update element at index 2
55+
// docA updates it to 8
56+
// docB updates it to 9
57+
// docA.toJson() should return { list: [0, 1, 8] }
58+
// docB.toJson() should return { list: [0, 1, 9] }
59+
60+
listA.set(2, 8);
61+
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8] });
62+
listB.set(2, 9);
63+
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });
64+
}
65+
66+
{
67+
// Merge docA and docB
68+
docA.import(docB.exportFrom(docA.version()));
69+
docB.import(docA.exportFrom(docB.version()));
70+
}
71+
72+
// Converge to [0, 1, 9] because docB has larger peerId thus larger logical time
73+
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 9] });
74+
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });
75+
76+
{
77+
// Concurrently docA and docB move element at index 0
78+
// docA moves it to 2
79+
// docB moves it to 1
80+
// docA.toJson() should return { list: [1, 9, 0] }
81+
// docB.toJson() should return { list: [1, 0, 9] }
82+
83+
listA.move(0, 2);
84+
listB.move(0, 1);
85+
expect(docA.toJson()).toStrictEqual({ list: [1, 9, 0] });
86+
expect(docB.toJson()).toStrictEqual({ list: [1, 0, 9] });
87+
}
88+
89+
{
90+
// Merge docA and docB
91+
docA.import(docB.exportFrom(docA.version()));
92+
docB.import(docA.exportFrom(docB.version()));
93+
}
94+
95+
// Converge to [1, 0, 9] because docB has larger peerId thus larger logical time
96+
expect(docA.toJson()).toStrictEqual({ list: [1, 0, 9] });
97+
expect(docB.toJson()).toStrictEqual({ list: [1, 0, 9] });
98+
})
99+
100+
101+
Deno.test("List Cursors", () => {
20102
const doc = new Loro();
21-
const map = doc.getMap("root");
22-
list = map.setContainer("list", list);
23-
expect(doc.toJson()).toStrictEqual({ root: { list: [0, "1"] } });
24-
list.move(0, 1);
25-
expect(doc.toJson()).toStrictEqual({ root: { list: ["1", 0] } });
26-
// Uint8Array is a special type in Loro
27-
list.set(1, new Uint8Array([1, 2, 3]));
28-
expect(doc.toJson()).toStrictEqual({ root: { list: ["1", new Uint8Array([1, 2, 3])] } });
29-
const text = list.setContainer(0, new LoroText());
30-
text.insert(0, "Hello")
31-
expect(doc.toJson()).toStrictEqual({ root: { list: ["Hello", new Uint8Array([1, 2, 3])] } });
103+
doc.setPeerId("1");
104+
const list = doc.getList("list");
105+
list.push("Hello");
106+
list.push("World");
107+
const cursor = list.getCursor(1)!;
108+
expect(cursor.pos()).toStrictEqual({ peer: "1", counter: 1 });
109+
110+
const encodedCursor: Uint8Array = cursor.encode();
111+
const exported: Uint8Array = doc.exportSnapshot();
112+
113+
// Sending the exported snapshot and the encoded cursor to peer 2
114+
// Peer 2 will decode the cursor and get the position of the cursor in the list
115+
// Peer 2 will then insert "Hello" at the beginning of the list
116+
117+
const docB = new Loro();
118+
docB.setPeerId("2");
119+
const listB = docB.getList("list");
120+
docB.import(exported);
121+
listB.insert(0, "Foo");
122+
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Hello", "World"] });
123+
const cursorB = Cursor.decode(encodedCursor);
124+
{
125+
// The cursor position is shifted to the right by 1
126+
const pos = docB.getCursorPos(cursorB);
127+
expect(pos.offset).toBe(2);
128+
}
129+
listB.insert(1, "Bar");
130+
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Bar", "Hello", "World"] });
131+
{
132+
// The cursor position is shifted to the right by 1
133+
const pos = docB.getCursorPos(cursorB);
134+
expect(pos.offset).toBe(3);
135+
}
136+
listB.delete(3, 1);
137+
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Bar", "Hello"] });
138+
{
139+
// The position cursor points to is now deleted,
140+
// but it should still get the position
141+
const pos = docB.getCursorPos(cursorB);
142+
expect(pos.offset).toBe(3);
143+
// It will also offer a update on the cursor position.
144+
//
145+
// It's because the old cursor position is deleted, `doc.getCursorPos()` will slow down over time.
146+
// Internally, it needs to traverse the related history to find the correct position for a deleted
147+
// cursor position.
148+
//
149+
// After refreshing the cursor, the performance of `doc.getCursorPos()` will improve.
150+
expect(pos.update).toBeDefined();
151+
const newCursor: Cursor = pos.update!;
152+
// The new cursor position is undefined because the cursor is at the end of the list
153+
expect(newCursor.pos()).toBeUndefined();
154+
// The side is 1 because the cursor is at the right end of the list
155+
expect(newCursor.side()).toBe(1);
156+
157+
const newPos = docB.getCursorPos(newCursor);
158+
// The offset doesn't changed
159+
expect(newPos.offset).toBe(3);
160+
// The update is undefined because the cursor no longer needs to be updated
161+
expect(newPos.update).toBeUndefined();
162+
}
32163
})
33164

08_map.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Loro, LoroText } from "npm:[email protected]";
2+
import { expect } from "npm:[email protected]";
3+
4+
Deno.test("LoroMap", () => {
5+
const docA = new Loro();
6+
docA.setPeerId("0");
7+
const docB = new Loro();
8+
docB.setPeerId("1");
9+
10+
const mapA = docA.getMap("map");
11+
const mapB = docB.getMap("map");
12+
13+
mapA.set("a", 1);
14+
const textB = mapB.setContainer("a", new LoroText());
15+
textB.insert(0, "Hi");
16+
17+
expect(docA.toJson()).toStrictEqual({ map: { a: 1 } });
18+
expect(docB.toJson()).toStrictEqual({ map: { a: "Hi" } });
19+
20+
docA.import(docB.exportSnapshot());
21+
docB.import(docA.exportSnapshot());
22+
23+
expect(docA.toJson()).toStrictEqual({ map: { a: "Hi" } });
24+
expect(docB.toJson()).toStrictEqual({ map: { a: "Hi" } });
25+
});
26+

09_composition.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Loro, LoroList, LoroText } from "npm:[email protected]";
2+
import { expect } from "npm:[email protected]";
3+
4+
Deno.test("Composition", async () => {
5+
const doc = new Loro();
6+
const map = doc.getMap("map");
7+
let callTimes = 0;
8+
map.subscribe((_event) => {
9+
callTimes++;
10+
});
11+
12+
// Create a sub container for map
13+
// { map: { list: [] } }
14+
const list = map.setContainer("list", new LoroList());
15+
list.push(0);
16+
list.push(1);
17+
18+
// Create a sub container for list
19+
// { map: { list: [0, 1, LoroText] } }
20+
const text = list.insertContainer(2, new LoroText());
21+
expect(doc.toJson()).toStrictEqual({ map: { list: [0, 1, ""] } });
22+
{
23+
// Commit will trigger the event, because list is a sub container of map
24+
doc.commit();
25+
await new Promise((resolve) => setTimeout(resolve, 1));
26+
expect(callTimes).toBe(1);
27+
}
28+
29+
text.insert(0, "Hello, ");
30+
text.insert(7, "World!");
31+
expect(doc.toJson()).toStrictEqual({ map: { list: [0, 1, "Hello, World!"] } });
32+
{
33+
// Commit will trigger the event, because text is a descendant of map
34+
doc.commit();
35+
await new Promise((resolve) => setTimeout(resolve, 1));
36+
expect(callTimes).toBe(2);
37+
}
38+
});
39+

0 commit comments

Comments
 (0)