Skip to content

Commit

Permalink
docs: update examples on list and map
Browse files Browse the repository at this point in the history
  • Loading branch information
zxch3n committed Apr 26, 2024
1 parent db0b0d5 commit 8338c05
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 24 deletions.
179 changes: 155 additions & 24 deletions 07_list.test.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,164 @@
import { Loro, LoroList, LoroMovableList, LoroText } from "npm:[email protected]";
import { Cursor, Loro } from "npm:[email protected]";
import { expect } from "npm:[email protected]";

Deno.test("List", () => {
let list = new LoroList();
list.push(0);
list.push("1");
const doc = new Loro();
const map = doc.getMap("root");
list = map.setContainer("list", list);
expect(doc.toJson()).toStrictEqual({ root: { list: [0, "1"] } });
list.delete(0, 1);
expect(doc.toJson()).toStrictEqual({ root: { list: ["1"] } });
const docA = new Loro();
docA.setPeerId("1");
const listA = docA.getList("list");
listA.push(0);
listA.push(1);
listA.push(2);
const bytes: Uint8Array = docA.exportSnapshot();

const docB = Loro.fromSnapshot(bytes);
docB.setPeerId("2");
const listB = docB.getList("list");
{
// Concurrently docA and docB update element at index 2
// docA updates it to 8
// docB updates it to 9
// docA.toJson() should return { list: [0, 1, 8] }
// docB.toJson() should return { list: [0, 1, 9] }

listB.delete(2, 1);
listB.insert(2, 9);
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });
listA.delete(2, 1);
listA.insert(2, 8);
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8] });
}

{
// Merge docA and docB
docA.import(docB.exportFrom(docA.version()));
docB.import(docA.exportFrom(docB.version()));
}

expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8, 9] });
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 8, 9] });
})

Deno.test("MovableList", () => {
let list = new LoroMovableList();
list.push(0);
list.push("1");
const docA = new Loro();
docA.setPeerId("1");
const listA = docA.getMovableList("list");
listA.push(0);
listA.push(1);
listA.push(2);
const bytes: Uint8Array = docA.exportSnapshot();

const docB = Loro.fromSnapshot(bytes);
docB.setPeerId("2");
const listB = docB.getMovableList("list");
{
// Concurrently docA and docB update element at index 2
// docA updates it to 8
// docB updates it to 9
// docA.toJson() should return { list: [0, 1, 8] }
// docB.toJson() should return { list: [0, 1, 9] }

listA.set(2, 8);
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 8] });
listB.set(2, 9);
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });
}

{
// Merge docA and docB
docA.import(docB.exportFrom(docA.version()));
docB.import(docA.exportFrom(docB.version()));
}

// Converge to [0, 1, 9] because docB has larger peerId thus larger logical time
expect(docA.toJson()).toStrictEqual({ list: [0, 1, 9] });
expect(docB.toJson()).toStrictEqual({ list: [0, 1, 9] });

{
// Concurrently docA and docB move element at index 0
// docA moves it to 2
// docB moves it to 1
// docA.toJson() should return { list: [1, 9, 0] }
// docB.toJson() should return { list: [1, 0, 9] }

listA.move(0, 2);
listB.move(0, 1);
expect(docA.toJson()).toStrictEqual({ list: [1, 9, 0] });
expect(docB.toJson()).toStrictEqual({ list: [1, 0, 9] });
}

{
// Merge docA and docB
docA.import(docB.exportFrom(docA.version()));
docB.import(docA.exportFrom(docB.version()));
}

// Converge to [1, 0, 9] because docB has larger peerId thus larger logical time
expect(docA.toJson()).toStrictEqual({ list: [1, 0, 9] });
expect(docB.toJson()).toStrictEqual({ list: [1, 0, 9] });
})


Deno.test("List Cursors", () => {
const doc = new Loro();
const map = doc.getMap("root");
list = map.setContainer("list", list);
expect(doc.toJson()).toStrictEqual({ root: { list: [0, "1"] } });
list.move(0, 1);
expect(doc.toJson()).toStrictEqual({ root: { list: ["1", 0] } });
// Uint8Array is a special type in Loro
list.set(1, new Uint8Array([1, 2, 3]));
expect(doc.toJson()).toStrictEqual({ root: { list: ["1", new Uint8Array([1, 2, 3])] } });
const text = list.setContainer(0, new LoroText());
text.insert(0, "Hello")
expect(doc.toJson()).toStrictEqual({ root: { list: ["Hello", new Uint8Array([1, 2, 3])] } });
doc.setPeerId("1");
const list = doc.getList("list");
list.push("Hello");
list.push("World");
const cursor = list.getCursor(1)!;
expect(cursor.pos()).toStrictEqual({ peer: "1", counter: 1 });

const encodedCursor: Uint8Array = cursor.encode();
const exported: Uint8Array = doc.exportSnapshot();

// Sending the exported snapshot and the encoded cursor to peer 2
// Peer 2 will decode the cursor and get the position of the cursor in the list
// Peer 2 will then insert "Hello" at the beginning of the list

const docB = new Loro();
docB.setPeerId("2");
const listB = docB.getList("list");
docB.import(exported);
listB.insert(0, "Foo");
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Hello", "World"] });
const cursorB = Cursor.decode(encodedCursor);
{
// The cursor position is shifted to the right by 1
const pos = docB.getCursorPos(cursorB);
expect(pos.offset).toBe(2);
}
listB.insert(1, "Bar");
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Bar", "Hello", "World"] });
{
// The cursor position is shifted to the right by 1
const pos = docB.getCursorPos(cursorB);
expect(pos.offset).toBe(3);
}
listB.delete(3, 1);
expect(docB.toJson()).toStrictEqual({ list: ["Foo", "Bar", "Hello"] });
{
// The position cursor points to is now deleted,
// but it should still get the position
const pos = docB.getCursorPos(cursorB);
expect(pos.offset).toBe(3);
// It will also offer a update on the cursor position.
//
// It's because the old cursor position is deleted, `doc.getCursorPos()` will slow down over time.
// Internally, it needs to traverse the related history to find the correct position for a deleted
// cursor position.
//
// After refreshing the cursor, the performance of `doc.getCursorPos()` will improve.
expect(pos.update).toBeDefined();
const newCursor: Cursor = pos.update!;
// The new cursor position is undefined because the cursor is at the end of the list
expect(newCursor.pos()).toBeUndefined();
// The side is 1 because the cursor is at the right end of the list
expect(newCursor.side()).toBe(1);

const newPos = docB.getCursorPos(newCursor);
// The offset doesn't changed
expect(newPos.offset).toBe(3);
// The update is undefined because the cursor no longer needs to be updated
expect(newPos.update).toBeUndefined();
}
})

26 changes: 26 additions & 0 deletions 08_map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Loro, LoroText } from "npm:[email protected]";
import { expect } from "npm:[email protected]";

Deno.test("LoroMap", () => {
const docA = new Loro();
docA.setPeerId("0");
const docB = new Loro();
docB.setPeerId("1");

const mapA = docA.getMap("map");
const mapB = docB.getMap("map");

mapA.set("a", 1);
const textB = mapB.setContainer("a", new LoroText());
textB.insert(0, "Hi");

expect(docA.toJson()).toStrictEqual({ map: { a: 1 } });
expect(docB.toJson()).toStrictEqual({ map: { a: "Hi" } });

docA.import(docB.exportSnapshot());
docB.import(docA.exportSnapshot());

expect(docA.toJson()).toStrictEqual({ map: { a: "Hi" } });
expect(docB.toJson()).toStrictEqual({ map: { a: "Hi" } });
});

39 changes: 39 additions & 0 deletions 09_composition.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Loro, LoroList, LoroText } from "npm:[email protected]";
import { expect } from "npm:[email protected]";

Deno.test("Composition", async () => {
const doc = new Loro();
const map = doc.getMap("map");
let callTimes = 0;
map.subscribe((_event) => {
callTimes++;
});

// Create a sub container for map
// { map: { list: [] } }
const list = map.setContainer("list", new LoroList());
list.push(0);
list.push(1);

// Create a sub container for list
// { map: { list: [0, 1, LoroText] } }
const text = list.insertContainer(2, new LoroText());
expect(doc.toJson()).toStrictEqual({ map: { list: [0, 1, ""] } });
{
// Commit will trigger the event, because list is a sub container of map
doc.commit();
await new Promise((resolve) => setTimeout(resolve, 1));
expect(callTimes).toBe(1);
}

text.insert(0, "Hello, ");
text.insert(7, "World!");
expect(doc.toJson()).toStrictEqual({ map: { list: [0, 1, "Hello, World!"] } });
{
// Commit will trigger the event, because text is a descendant of map
doc.commit();
await new Promise((resolve) => setTimeout(resolve, 1));
expect(callTimes).toBe(2);
}
});

0 comments on commit 8338c05

Please sign in to comment.