loro/loro-js/tests/basic.test.ts

378 lines
10 KiB
TypeScript
Raw Normal View History

2023-12-05 06:28:39 +00:00
import { describe, expect, expectTypeOf, it } from "vitest";
import {
Loro,
LoroList,
LoroMap,
isContainer,
2023-12-05 06:28:39 +00:00
getType,
VersionVector,
} from "../src";
import { Container } from "../dist/loro";
2023-11-07 11:48:16 +00:00
2023-11-10 12:13:01 +00:00
it("basic example", () => {
const doc = new Loro();
const list: LoroList = doc.getList("list");
list.insert(0, "A");
list.insert(1, "B");
list.insert(2, "C");
const map: LoroMap = doc.getMap("map");
// map can only has string key
map.set("key", "value");
expect(doc.toJson()).toStrictEqual({
list: ["A", "B", "C"],
map: { key: "value" },
2023-11-10 12:13:01 +00:00
});
2023-11-11 07:16:28 +00:00
// delete 2 elements at index 0
list.delete(0, 2);
2023-11-10 12:13:01 +00:00
expect(doc.toJson()).toStrictEqual({
list: ["C"],
map: { key: "value" },
2023-11-10 12:13:01 +00:00
});
// Insert a text container to the list
const text = list.insertContainer(0, "Text");
text.insert(0, "Hello");
text.insert(0, "Hi! ");
2023-11-10 12:13:01 +00:00
// delete 1 element at index 0
expect(doc.toJson()).toStrictEqual({
list: ["Hi! Hello", "C"],
map: { key: "value" },
2023-11-10 12:13:01 +00:00
});
// Insert a list container to the map
const list2 = map.setContainer("test", "List");
list2.insert(0, 1);
expect(doc.toJson()).toStrictEqual({
list: ["Hi! Hello", "C"],
map: { key: "value", test: [1] },
2023-11-10 12:13:01 +00:00
});
});
2023-11-10 12:13:01 +00:00
it("basic sync example", () => {
const docA = new Loro();
const docB = new Loro();
const listA: LoroList = docA.getList("list");
listA.insert(0, "A");
listA.insert(1, "B");
listA.insert(2, "C");
// B import the ops from A
docB.import(docA.exportFrom());
expect(docB.toJson()).toStrictEqual({
list: ["A", "B", "C"],
});
2023-11-10 12:13:01 +00:00
const listB: LoroList = docB.getList("list");
// delete 1 element at index 1
listB.delete(1, 1);
// A import the ops from B
docA.import(docB.exportFrom(docA.version()));
2023-11-10 12:13:01 +00:00
// list at A is now ["A", "C"], with the same state as B
expect(docA.toJson()).toStrictEqual({
list: ["A", "C"],
2023-11-10 12:13:01 +00:00
});
expect(docA.toJson()).toStrictEqual(docB.toJson());
});
2023-11-10 12:13:01 +00:00
it("basic events", () => {
const doc = new Loro();
doc.subscribe((event) => {});
2023-11-10 12:13:01 +00:00
const list = doc.getList("list");
});
2023-11-10 12:13:01 +00:00
2023-11-07 11:48:16 +00:00
describe("list", () => {
it("insert containers", () => {
const doc = new Loro();
const list = doc.getList("list");
const map = list.insertContainer(0, "Map");
map.set("key", "value");
2023-11-16 14:25:37 +00:00
const v = list.get(0) as LoroMap;
2023-11-07 11:48:16 +00:00
console.log(v);
expect(v instanceof LoroMap).toBeTruthy();
expect(v.toJson()).toStrictEqual({ key: "value" });
});
2023-11-07 11:48:16 +00:00
it("toArray", () => {
const doc = new Loro();
const list = doc.getList("list");
list.insert(0, 1);
list.insert(1, 2);
expect(list.toArray()).toStrictEqual([1, 2]);
list.insertContainer(2, "Text");
const t = list.toArray()[2];
expect(isContainer(t)).toBeTruthy();
2023-12-05 06:28:39 +00:00
expect(getType(t)).toBe("Text");
expect(getType(123)).toBe("Json");
});
});
describe("map", () => {
it("get child container", () => {
const doc = new Loro();
const map = doc.getMap("map");
const list = map.setContainer("key", "List");
list.insert(0, 1);
expect(map.get("key") instanceof LoroList).toBeTruthy();
expect((map.get("key") as LoroList).toJson()).toStrictEqual([1]);
});
2023-11-16 14:25:37 +00:00
it("set large int", () => {
const doc = new Loro();
const map = doc.getMap("map");
map.set("key", 2147483699);
expect(map.get("key")).toBe(2147483699);
});
});
describe("import", () => {
it("pending", () => {
const a = new Loro();
a.getText("text").insert(0, "a");
const b = new Loro();
b.import(a.exportFrom());
b.getText("text").insert(1, "b");
const c = new Loro();
c.import(b.exportFrom());
c.getText("text").insert(2, "c");
// c export from b's version, which cannot be imported directly to a.
// This operation is pending.
a.import(c.exportFrom(b.version()));
expect(a.getText("text").toString()).toBe("a");
// a import the missing ops from b. It makes the pending operation from c valid.
a.import(b.exportFrom(a.version()));
expect(a.getText("text").toString()).toBe("abc");
});
2023-11-20 09:44:24 +00:00
it("import by frontiers", () => {
const a = new Loro();
a.getText("text").insert(0, "a");
const b = new Loro();
b.import(a.exportFrom());
b.getText("text").insert(1, "b");
b.getList("list").insert(0, [1, 2]);
const updates = b.exportFrom(b.frontiersToVV(a.frontiers()));
2023-11-20 09:44:24 +00:00
a.import(updates);
expect(a.toJson()).toStrictEqual(b.toJson());
});
it("from snapshot", () => {
const a = new Loro();
a.getText("text").insert(0, "hello");
const bytes = a.exportSnapshot();
const b = Loro.fromSnapshot(bytes);
b.getText("text").insert(0, "123");
expect(b.toJson()).toStrictEqual({ text: "123hello" });
});
it("importBatch Error #181", () => {
const docA = new Loro();
const updateA = docA.exportSnapshot();
const docB = new Loro();
docB.importUpdateBatch([updateA]);
docB.getText("text").insert(0, "hello");
docB.commit();
console.log(docB.exportFrom());
});
});
describe("map", () => {
it("keys", () => {
const doc = new Loro();
const map = doc.getMap("map");
map.set("foo", "bar");
map.set("baz", "bar");
const entries = map.keys();
expect(entries).toStrictEqual(["foo", "baz"]);
});
it("values", () => {
const doc = new Loro();
const map = doc.getMap("map");
map.set("foo", "bar");
map.set("baz", "bar");
const entries = map.values();
expect(entries).toStrictEqual(["bar", "bar"]);
});
it("entries", () => {
const doc = new Loro();
const map = doc.getMap("map");
map.set("foo", "bar");
map.set("baz", "bar");
map.set("new", 11);
map.delete("new");
const entries = map.entries();
expect(entries).toStrictEqual([
["foo", "bar"],
["baz", "bar"],
]);
});
it("entries should return container handlers", () => {
const doc = new Loro();
const map = doc.getMap("map");
map.setContainer("text", "Text");
map.set("foo", "bar");
const entries = map.entries();
expect((entries[0][1]! as Container).kind() === "Text").toBeTruthy();
});
});
it("handlers should still be usable after doc is dropped", () => {
const doc = new Loro();
const text = doc.getText("text");
const list = doc.getList("list");
const map = doc.getMap("map");
doc.free();
text.insert(0, "123");
expect(text.toString()).toBe("123");
list.insert(0, 1);
expect(list.toJson()).toStrictEqual([1]);
map.set("k", 8);
expect(map.toJson()).toStrictEqual({ k: 8 });
});
it("get change with given lamport", () => {
const doc1 = new Loro();
doc1.setPeerId(1);
const doc2 = new Loro();
doc2.setPeerId(2);
doc1.getText("text").insert(0, "01234");
doc2.import(doc1.exportFrom());
doc2.getText("text").insert(0, "56789");
doc1.import(doc2.exportFrom());
doc1.getText("text").insert(0, "01234");
doc1.commit();
{
const change = doc1.getChangeAtLamport("1", 1)!;
expect(change.lamport).toBe(0);
expect(change.peer).toBe("1");
expect(change.length).toBe(5);
}
{
const change = doc1.getChangeAtLamport("1", 7)!;
expect(change.lamport).toBe(0);
expect(change.peer).toBe("1");
expect(change.length).toBe(5);
}
{
const change = doc1.getChangeAtLamport("1", 10)!;
expect(change.lamport).toBe(10);
expect(change.peer).toBe("1");
expect(change.length).toBe(5);
}
{
const change = doc1.getChangeAtLamport("1", 13)!;
expect(change.lamport).toBe(10);
expect(change.peer).toBe("1");
expect(change.length).toBe(5);
}
{
const change = doc1.getChangeAtLamport("1", 20)!;
expect(change.lamport).toBe(10);
expect(change.peer).toBe("1");
expect(change.length).toBe(5);
}
{
const change = doc1.getChangeAtLamport("111", 13);
expect(change).toBeUndefined();
}
});
2023-12-05 06:28:39 +00:00
it("isContainer", () => {
expect(isContainer("123")).toBeFalsy();
expect(isContainer(123)).toBeFalsy();
expect(isContainer(123n)).toBeFalsy();
expect(isContainer(new Map())).toBeFalsy();
expect(isContainer(new Set())).toBeFalsy();
expect(isContainer({})).toBeFalsy();
expect(isContainer(undefined)).toBeFalsy();
expect(isContainer(null)).toBeFalsy();
const doc = new Loro();
const t = doc.getText("t");
expect(isContainer(t)).toBeTruthy();
expect(getType(t)).toBe("Text");
expect(getType(123)).toBe("Json");
});
2023-12-05 06:28:39 +00:00
it("getValueType", () => {
// Type tests
const doc = new Loro();
const t = doc.getText("t");
expectTypeOf(getType(t)).toEqualTypeOf<"Text">();
expect(getType(t)).toBe("Text");
expectTypeOf(getType(123)).toEqualTypeOf<"Json">();
expect(getType(123)).toBe("Json");
expectTypeOf(getType(undefined)).toEqualTypeOf<"Json">();
expect(getType(undefined)).toBe("Json");
expectTypeOf(getType(null)).toEqualTypeOf<"Json">();
expect(getType(null)).toBe("Json");
expectTypeOf(getType({})).toEqualTypeOf<"Json">();
expect(getType({})).toBe("Json");
const map = doc.getMap("map");
const list = doc.getList("list");
const tree = doc.getTree("tree");
expectTypeOf(getType(map)).toEqualTypeOf<"Map">();
expect(getType(map)).toBe("Map");
expectTypeOf(getType(list)).toEqualTypeOf<"List">();
expect(getType(list)).toBe("List");
expectTypeOf(getType(tree)).toEqualTypeOf<"Tree">();
expect(getType(tree)).toBe("Tree");
});
it("enable timestamp", () => {
const doc = new Loro();
doc.setPeerId(1);
doc.getText("123").insert(0, "123");
doc.commit();
{
const c = doc.getChangeAt({ peer: "1", counter: 0 });
expect(c.timestamp).toBe(0);
}
doc.setRecordTimestamp(true);
doc.getText("123").insert(0, "123");
doc.commit();
{
const c = doc.getChangeAt({ peer: "1", counter: 4 });
expect(c.timestamp).toBeCloseTo(Date.now(), 0);
}
});
it("commit with specified timestamp", () => {
const doc = new Loro();
doc.setPeerId(1);
doc.getText("123").insert(0, "123");
doc.commit(undefined, 111);
const c = doc.getChangeAt({ peer: "1", counter: 0 });
expect(c.timestamp).toBe(111);
});
it("can control the mergeable interval", () => {
{
const doc = new Loro();
doc.setPeerId(1);
doc.getText("123").insert(0, "1");
doc.commit(undefined, 110);
doc.getText("123").insert(0, "1");
doc.commit(undefined, 120);
expect(doc.getAllChanges().get("1")?.length).toBe(1);
}
{
const doc = new Loro();
doc.setPeerId(1);
doc.setChangeMergeInterval(10);
doc.getText("123").insert(0, "1");
doc.commit(undefined, 110);
doc.getText("123").insert(0, "1");
doc.commit(undefined, 120);
expect(doc.getAllChanges().get("1")?.length).toBe(2);
}
});