automerge/javascript/test/basic_test.ts
Alex Good 6c0d102032
automerge-js: Add backwards compatibility text layer
The new text features are faster and more ergonomic but not backwards
compatible. In order to make them backwards compatible re-expose the
original functionality and move the new API under a `future` export.
This allows users to interoperably use both implementations.
2023-01-10 12:52:21 +00:00

473 lines
14 KiB
TypeScript

import * as assert from "assert"
import { unstable as Automerge } from "../src"
import * as WASM from "@automerge/automerge-wasm"
describe("Automerge", () => {
describe("basics", () => {
it("should init clone and free", () => {
let doc1 = Automerge.init()
let doc2 = Automerge.clone(doc1)
// this is only needed if weakrefs are not supported
Automerge.free(doc1)
Automerge.free(doc2)
})
it("should be able to make a view with specifc heads", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => (d.value = 1))
let heads2 = Automerge.getHeads(doc2)
let doc3 = Automerge.change(doc2, d => (d.value = 2))
let doc2_v2 = Automerge.view(doc3, heads2)
assert.deepEqual(doc2, doc2_v2)
let doc2_v2_clone = Automerge.clone(doc2, "aabbcc")
assert.deepEqual(doc2, doc2_v2_clone)
assert.equal(Automerge.getActorId(doc2_v2_clone), "aabbcc")
})
it("should allow you to change a clone of a view", () => {
let doc1 = Automerge.init<any>()
doc1 = Automerge.change(doc1, d => (d.key = "value"))
let heads = Automerge.getHeads(doc1)
doc1 = Automerge.change(doc1, d => (d.key = "value2"))
let fork = Automerge.clone(Automerge.view(doc1, heads))
assert.deepEqual(fork, { key: "value" })
fork = Automerge.change(fork, d => (d.key = "value3"))
assert.deepEqual(fork, { key: "value3" })
})
it("handle basic set and read on root object", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.hello = "world"
d.big = "little"
d.zip = "zop"
d.app = "dap"
assert.deepEqual(d, {
hello: "world",
big: "little",
zip: "zop",
app: "dap",
})
})
assert.deepEqual(doc2, {
hello: "world",
big: "little",
zip: "zop",
app: "dap",
})
})
it("can detect an automerge doc with isAutomerge()", () => {
const doc1 = Automerge.from({ sub: { object: true } })
assert(Automerge.isAutomerge(doc1))
assert(!Automerge.isAutomerge(doc1.sub))
assert(!Automerge.isAutomerge("String"))
assert(!Automerge.isAutomerge({ sub: { object: true } }))
assert(!Automerge.isAutomerge(undefined))
const jsObj = Automerge.toJS(doc1)
assert(!Automerge.isAutomerge(jsObj))
assert.deepEqual(jsObj, doc1)
})
it("it should recursively freeze the document if requested", () => {
let doc1 = Automerge.init<any>({ freeze: true })
let doc2 = Automerge.init<any>()
assert(Object.isFrozen(doc1))
assert(!Object.isFrozen(doc2))
// will also freeze sub objects
doc1 = Automerge.change(
doc1,
doc => (doc.book = { title: "how to win friends" })
)
doc2 = Automerge.merge(doc2, doc1)
assert(Object.isFrozen(doc1))
assert(Object.isFrozen(doc1.book))
assert(!Object.isFrozen(doc2))
assert(!Object.isFrozen(doc2.book))
// works on from
let doc3 = Automerge.from({ sub: { obj: "inner" } }, { freeze: true })
assert(Object.isFrozen(doc3))
assert(Object.isFrozen(doc3.sub))
// works on load
let doc4 = Automerge.load<any>(Automerge.save(doc3), { freeze: true })
assert(Object.isFrozen(doc4))
assert(Object.isFrozen(doc4.sub))
// follows clone
let doc5 = Automerge.clone(doc4)
assert(Object.isFrozen(doc5))
assert(Object.isFrozen(doc5.sub))
// toJS does not freeze
let exported = Automerge.toJS(doc5)
assert(!Object.isFrozen(exported))
})
it("handle basic sets over many changes", () => {
let doc1 = Automerge.init<any>()
let timestamp = new Date()
let counter = new Automerge.Counter(100)
let bytes = new Uint8Array([10, 11, 12])
let doc2 = Automerge.change(doc1, d => {
d.hello = "world"
})
let doc3 = Automerge.change(doc2, d => {
d.counter1 = counter
})
let doc4 = Automerge.change(doc3, d => {
d.timestamp1 = timestamp
})
let doc5 = Automerge.change(doc4, d => {
d.app = null
})
let doc6 = Automerge.change(doc5, d => {
d.bytes1 = bytes
})
let doc7 = Automerge.change(doc6, d => {
d.uint = new Automerge.Uint(1)
d.int = new Automerge.Int(-1)
d.float64 = new Automerge.Float64(5.5)
d.number1 = 100
d.number2 = -45.67
d.true = true
d.false = false
})
assert.deepEqual(doc7, {
hello: "world",
true: true,
false: false,
int: -1,
uint: 1,
float64: 5.5,
number1: 100,
number2: -45.67,
counter1: counter,
timestamp1: timestamp,
bytes1: bytes,
app: null,
})
let changes = Automerge.getAllChanges(doc7)
let t1 = Automerge.init()
let [t2] = Automerge.applyChanges(t1, changes)
assert.deepEqual(doc7, t2)
})
it("handle overwrites to values", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.hello = "world1"
})
let doc3 = Automerge.change(doc2, d => {
d.hello = "world2"
})
let doc4 = Automerge.change(doc3, d => {
d.hello = "world3"
})
let doc5 = Automerge.change(doc4, d => {
d.hello = "world4"
})
assert.deepEqual(doc5, { hello: "world4" })
})
it("handle set with object value", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.subobj = { hello: "world", subsubobj: { zip: "zop" } }
})
assert.deepEqual(doc2, {
subobj: { hello: "world", subsubobj: { zip: "zop" } },
})
})
it("handle simple list creation", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => (d.list = []))
assert.deepEqual(doc2, { list: [] })
})
it("handle simple lists", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.list = [1, 2, 3]
})
assert.deepEqual(doc2.list.length, 3)
assert.deepEqual(doc2.list[0], 1)
assert.deepEqual(doc2.list[1], 2)
assert.deepEqual(doc2.list[2], 3)
assert.deepEqual(doc2, { list: [1, 2, 3] })
// assert.deepStrictEqual(Automerge.toJS(doc2), { list: [1,2,3] })
let doc3 = Automerge.change(doc2, d => {
d.list[1] = "a"
})
assert.deepEqual(doc3.list.length, 3)
assert.deepEqual(doc3.list[0], 1)
assert.deepEqual(doc3.list[1], "a")
assert.deepEqual(doc3.list[2], 3)
assert.deepEqual(doc3, { list: [1, "a", 3] })
})
it("handle simple lists", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.list = [1, 2, 3]
})
let changes = Automerge.getChanges(doc1, doc2)
let docB1 = Automerge.init()
let [docB2] = Automerge.applyChanges(docB1, changes)
assert.deepEqual(docB2, doc2)
})
it("handle text", () => {
let doc1 = Automerge.init<any>()
let doc2 = Automerge.change(doc1, d => {
d.list = "hello"
Automerge.splice(d, "list", 2, 0, "Z")
})
let changes = Automerge.getChanges(doc1, doc2)
let docB1 = Automerge.init()
let [docB2] = Automerge.applyChanges(docB1, changes)
assert.deepEqual(docB2, doc2)
})
it("handle non-text strings", () => {
let doc1 = WASM.create(true)
doc1.put("_root", "text", "hello world")
let doc2 = Automerge.load<any>(doc1.save())
assert.throws(() => {
Automerge.change(doc2, d => {
Automerge.splice(d, "text", 1, 0, "Z")
})
}, /Cannot splice/)
})
it("have many list methods", () => {
let doc1 = Automerge.from({ list: [1, 2, 3] })
assert.deepEqual(doc1, { list: [1, 2, 3] })
let doc2 = Automerge.change(doc1, d => {
d.list.splice(1, 1, 9, 10)
})
assert.deepEqual(doc2, { list: [1, 9, 10, 3] })
let doc3 = Automerge.change(doc2, d => {
d.list.push(11, 12)
})
assert.deepEqual(doc3, { list: [1, 9, 10, 3, 11, 12] })
let doc4 = Automerge.change(doc3, d => {
d.list.unshift(2, 2)
})
assert.deepEqual(doc4, { list: [2, 2, 1, 9, 10, 3, 11, 12] })
let doc5 = Automerge.change(doc4, d => {
d.list.shift()
})
assert.deepEqual(doc5, { list: [2, 1, 9, 10, 3, 11, 12] })
let doc6 = Automerge.change(doc5, d => {
// @ts-ignore
d.list.insertAt(3, 100, 101)
})
assert.deepEqual(doc6, { list: [2, 1, 9, 100, 101, 10, 3, 11, 12] })
})
it("allows access to the backend", () => {
let doc = Automerge.init()
assert.deepEqual(Object.keys(Automerge.getBackend(doc)), ["ptr"])
})
it("lists and text have indexof", () => {
let doc = Automerge.from({
list: [0, 1, 2, 3, 4, 5, 6],
text: "hello world",
})
assert.deepEqual(doc.list.indexOf(5), 5)
assert.deepEqual(doc.text.indexOf("world"), 6)
})
})
describe("emptyChange", () => {
it("should generate a hash", () => {
let doc = Automerge.init()
doc = Automerge.change<any>(doc, d => {
d.key = "value"
})
Automerge.save(doc)
let headsBefore = Automerge.getHeads(doc)
headsBefore.sort()
doc = Automerge.emptyChange(doc, "empty change")
let headsAfter = Automerge.getHeads(doc)
headsAfter.sort()
assert.notDeepEqual(headsBefore, headsAfter)
})
})
describe("proxy lists", () => {
it("behave like arrays", () => {
let doc = Automerge.from({
chars: ["a", "b", "c"],
numbers: [20, 3, 100],
repeats: [20, 20, 3, 3, 3, 3, 100, 100],
})
let r1: Array<number> = []
doc = Automerge.change(doc, d => {
assert.deepEqual((d.chars as any[]).concat([1, 2]), [
"a",
"b",
"c",
1,
2,
])
assert.deepEqual(
d.chars.map(n => n + "!"),
["a!", "b!", "c!"]
)
assert.deepEqual(
d.numbers.map(n => n + 10),
[30, 13, 110]
)
assert.deepEqual(d.numbers.toString(), "20,3,100")
assert.deepEqual(d.numbers.toLocaleString(), "20,3,100")
assert.deepEqual(
d.numbers.forEach((n: number) => r1.push(n)),
undefined
)
assert.deepEqual(
d.numbers.every(n => n > 1),
true
)
assert.deepEqual(
d.numbers.every(n => n > 10),
false
)
assert.deepEqual(
d.numbers.filter(n => n > 10),
[20, 100]
)
assert.deepEqual(
d.repeats.find(n => n < 10),
3
)
assert.deepEqual(
d.repeats.find(n => n < 10),
3
)
assert.deepEqual(
d.repeats.find(n => n < 0),
undefined
)
assert.deepEqual(
d.repeats.findIndex(n => n < 10),
2
)
assert.deepEqual(
d.repeats.findIndex(n => n < 0),
-1
)
assert.deepEqual(
d.repeats.findIndex(n => n < 10),
2
)
assert.deepEqual(
d.repeats.findIndex(n => n < 0),
-1
)
assert.deepEqual(d.numbers.includes(3), true)
assert.deepEqual(d.numbers.includes(-3), false)
assert.deepEqual(d.numbers.join("|"), "20|3|100")
assert.deepEqual(d.numbers.join(), "20,3,100")
assert.deepEqual(
d.numbers.some(f => f === 3),
true
)
assert.deepEqual(
d.numbers.some(f => f < 0),
false
)
assert.deepEqual(
d.numbers.reduce((sum, n) => sum + n, 100),
223
)
assert.deepEqual(
d.repeats.reduce((sum, n) => sum + n, 100),
352
)
assert.deepEqual(
d.chars.reduce((sum, n) => sum + n, "="),
"=abc"
)
assert.deepEqual(
d.chars.reduceRight((sum, n) => sum + n, "="),
"=cba"
)
assert.deepEqual(
d.numbers.reduceRight((sum, n) => sum + n, 100),
223
)
assert.deepEqual(d.repeats.lastIndexOf(3), 5)
assert.deepEqual(d.repeats.lastIndexOf(3, 3), 3)
})
doc = Automerge.change(doc, d => {
assert.deepEqual(d.numbers.fill(-1, 1, 2), [20, -1, 100])
assert.deepEqual(d.chars.fill("z", 1, 100), ["a", "z", "z"])
})
assert.deepEqual(r1, [20, 3, 100])
assert.deepEqual(doc.numbers, [20, -1, 100])
assert.deepEqual(doc.chars, ["a", "z", "z"])
})
})
it("should obtain the same conflicts, regardless of merge order", () => {
let s1 = Automerge.init<any>()
let s2 = Automerge.init<any>()
s1 = Automerge.change(s1, doc => {
doc.x = 1
doc.y = 2
})
s2 = Automerge.change(s2, doc => {
doc.x = 3
doc.y = 4
})
const m1 = Automerge.merge(Automerge.clone(s1), Automerge.clone(s2))
const m2 = Automerge.merge(Automerge.clone(s2), Automerge.clone(s1))
assert.deepStrictEqual(
Automerge.getConflicts(m1, "x"),
Automerge.getConflicts(m2, "x")
)
})
describe("getObjectId", () => {
let s1 = Automerge.from({
string: "string",
number: 1,
null: null,
date: new Date(),
counter: new Automerge.Counter(),
bytes: new Uint8Array(10),
text: "",
list: [],
map: {},
})
it("should return null for scalar values", () => {
assert.equal(Automerge.getObjectId(s1.string), null)
assert.equal(Automerge.getObjectId(s1.number), null)
assert.equal(Automerge.getObjectId(s1.null!), null)
assert.equal(Automerge.getObjectId(s1.date), null)
assert.equal(Automerge.getObjectId(s1.counter), null)
assert.equal(Automerge.getObjectId(s1.bytes), null)
})
it("should return _root for the root object", () => {
assert.equal(Automerge.getObjectId(s1), "_root")
})
it("should return non-null for map, list, text, and objects", () => {
assert.equal(Automerge.getObjectId(s1.text), null)
assert.notEqual(Automerge.getObjectId(s1.list), null)
assert.notEqual(Automerge.getObjectId(s1.map), null)
})
})
})