automerge/automerge-wasm/test/patch.ts
2022-06-07 14:46:19 +02:00

170 lines
5.4 KiB
TypeScript

import { describe, it } from 'mocha';
//@ts-ignore
import assert from 'assert'
//@ts-ignore
import init, { create, load, SyncState, Automerge, encodeChange, decodeChange, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState, encodeSyncMessage } from '..'
import { Prop } from '..';
function patchValue(patch: any) : any {
switch (patch.datatype) {
case "map":
return {}
case "list":
return []
case "text":
return ""
default:
return patch.value
}
}
function patchTextValue(patch: any) : any {
if (typeof patch.value === "string" && patch.value.length == 1) {
return patch.value
} else {
return "\uFFFC"
}
}
function applyPatch(obj: any, path: Prop[], patch: any) : any {
let prop = path.shift();
if (typeof prop === 'number' && Array.isArray(obj)) {
return applyPatchToArray(obj, prop, path, patch)
}
if (typeof prop === 'number' && typeof obj === 'string') {
return applyPatchToText(obj, prop, path, patch)
}
if (typeof prop === 'string' && typeof obj === 'object') {
return applyPatchToObject(obj, prop, path, patch)
}
return obj
}
type Obj = { [key:string]: any }
function applyPatchToObject(obj: Obj, prop: string, path: Prop[], patch: any) : any {
if (path.length === 0) {
switch (patch.action) {
case "increment":
return { ... obj, [prop]: obj[prop] + patchValue(patch) }
case "put":
return { ... obj, [prop]: patchValue(patch) }
case "delete":
let tmp = { ... obj }
delete tmp[prop]
return tmp
default:
throw new RangeError(`Invalid patch ${patch}`)
}
} else {
return { ... obj, [prop]: applyPatch(obj[prop], path, patch) }
}
}
function applyPatchToArray(obj: Array<any>, prop: number, path: Prop[], patch: any) : any {
if (path.length === 0) {
switch (patch.action) {
case "increment":
return [ ... obj.slice(0,prop), obj[prop] + patchValue(patch), ... obj.slice(prop + 1) ]
case "put":
return [ ... obj.slice(0,prop), patchValue(patch), ... obj.slice(prop + 1) ]
case "insert":
return [ ... obj.slice(0,prop), patchValue(patch), ... obj.slice(prop) ]
case "delete":
return [... obj.slice(0,prop), ... obj.slice(prop + 1) ]
default:
throw new RangeError(`Invalid patch ${patch}`)
}
} else {
return [ ... obj.slice(0,prop), applyPatch(obj[prop], path, patch), ... obj.slice(prop + 1) ]
}
}
function applyPatchToText(obj: string, prop: number, path: Prop[], patch: any) : any {
if (path.length === 0) {
switch (patch.action) {
case "increment":
return obj
case "put":
return obj.slice(0,prop) + patchTextValue(patch) + obj.slice(prop + 1)
case "insert":
return obj.slice(0,prop) + patchTextValue(patch) + obj.slice(prop)
case "delete":
return obj.slice(0,prop) + obj.slice(prop + 1)
default:
throw new RangeError(`Invalid patch ${patch}`)
}
} else {
return obj
}
}
function applyPatches(obj: any, patches: any) {
for (let patch of patches) {
obj = applyPatch(obj, patch.path, patch)
}
return obj
}
describe('Automerge', () => {
describe('patches', () => {
it.only('can apply nested patches', () => {
const doc1 = create()
doc1.enablePatches(true)
doc1.put("/", "str", "value")
doc1.put("/", "num", 0)
doc1.delete("/", "num")
doc1.put("/", "counter", 0, "counter")
doc1.increment("/", "counter", 100)
doc1.increment("/", "counter", 1)
doc1.put("/", "bin", new Uint8Array([1,2,3]))
doc1.put("/", "bool", true)
let sub = doc1.putObject("/", "sub", {})
let list = doc1.putObject("/", "list", [1,2,3,4,5,6])
doc1.push("/list", 100, "counter");
doc1.increment("/list", 6, 10);
let sublist = doc1.putObject("/sub", "list", [1,2,3,4,[ 1,2,3,[4,{ five: "six" } ] ] ])
doc1.put(sub, "str", "value")
doc1.put("/sub", "num", 0)
doc1.put("/sub", "bin", new Uint8Array([1,2,3]))
doc1.put("/sub", "bool", true)
let subsub = doc1.putObject("/sub", "sub", {})
doc1.put("/sub/sub", "num", 0)
doc1.put("/sub/sub", "bin", new Uint8Array([1,2,3]))
doc1.put("/sub/sub", "bool", true)
let patches = doc1.popPatches()
let js = applyPatches({}, patches)
assert.deepEqual(js,doc1.materialize("/"))
})
it.only('can handle deletes with nested patches', () => {
const doc1 = create()
doc1.enablePatches(true)
let list = doc1.putObject("/", "list", [1,2,3,['a','b','c']])
doc1.delete("/list", 1);
doc1.push("/list", 'hello');
let patches = doc1.popPatches()
let js = applyPatches({}, patches)
assert.deepEqual(js,doc1.materialize("/"))
})
it.only('can handle patches with deletes withlists holding objects', () => {
const doc1 = create()
doc1.enablePatches(true)
let list = doc1.putObject("/", "list", [1,2,3,[{n:1},{n:2},{n:3}]])
doc1.delete("/list", 1);
doc1.put("/list/2/0", "n", 100);
doc1.delete("/list", 1);
doc1.put("/list/1/1", "n", 200);
doc1.insertObject("/list/1", 3, {n:400})
let text = doc1.putObject("/", "text", "hello world");
doc1.insertObject("/text", 3, {n:1})
let patches = doc1.popPatches()
console.log(doc1.materialize("/"))
let js = applyPatches({}, patches)
assert.deepEqual(js,doc1.materialize("/"))
})
})
})