import { describe, it } from 'mocha'; import assert from 'assert' import { create, Value } from '..' export const OBJECT_ID = Symbol.for('_am_objectId') // object containing metadata about current // @ts-ignore function _obj(doc: any) : any { if (typeof doc === 'object' && doc !== null) { return doc[OBJECT_ID] } } // sample classes for testing class Counter { value: number; constructor(n: number) { this.value = n } } describe('Automerge', () => { describe('Patch Apply', () => { it('apply nested sets on maps', () => { const start = { hello: { mellow: { yellow: "world", x: 1 }, y : 2 } } const doc1 = create() doc1.putObject("/", "hello", start.hello); let mat = doc1.materialize("/") const doc2 = create() doc2.enablePatches(true) doc2.merge(doc1) let base = doc2.applyPatches({}) assert.deepEqual(mat, start) assert.deepEqual(base, start) doc2.delete("/hello/mellow", "yellow"); // @ts-ignore delete start.hello.mellow.yellow; base = doc2.applyPatches(base) mat = doc2.materialize("/") assert.deepEqual(mat, start) assert.deepEqual(base, start) }) it('apply patches on lists', () => { const start = { list: [1,2,3,4] } const doc1 = create() doc1.putObject("/", "list", start.list); let mat = doc1.materialize("/") const doc2 = create() doc2.enablePatches(true) doc2.merge(doc1) mat = doc1.materialize("/") let base = doc2.applyPatches({}) assert.deepEqual(mat, start) assert.deepEqual(base, start) doc2.delete("/list", 3); start.list.splice(3,1) base = doc2.applyPatches(base) assert.deepEqual(base, start) }) it('apply patches on lists of lists of lists', () => { const start = { list: [ [ [ 1, 2, 3, 4, 5, 6], [ 7, 8, 9,10,11,12], ], [ [ 7, 8, 9,10,11,12], [ 1, 2, 3, 4, 5, 6], ] ] } const doc1 = create() doc1.enablePatches(true) doc1.putObject("/", "list", start.list); let base = doc1.applyPatches({}) let mat = doc1.clone().materialize("/") assert.deepEqual(mat, start) assert.deepEqual(base, start) doc1.delete("/list/0/1", 3) start.list[0][1].splice(3,1) doc1.delete("/list/0", 0) start.list[0].splice(0,1) mat = doc1.clone().materialize("/") base = doc1.applyPatches(base) assert.deepEqual(mat, start) assert.deepEqual(base, start) }) it('large inserts should make one splice patch', () => { const doc1 = create() doc1.enablePatches(true) doc1.putObject("/", "list", "abc"); const patches = doc1.popPatches() assert.deepEqual( patches, [ { action: 'put', path: [ 'list' ], value: "" }, { action: 'splice', path: [ 'list', 0 ], value: 'abc' }]) }) it('it should allow registering type wrappers', () => { const doc1 = create() doc1.enablePatches(true) doc1.registerDatatype("counter", (n: number) => new Counter(n)) const doc2 = doc1.fork() doc1.put("/", "n", 10, "counter") doc1.put("/", "m", 10, "int") let mat = doc1.materialize("/") assert.deepEqual( mat, { n: new Counter(10), m: 10 } ) doc2.merge(doc1) let apply = doc2.applyPatches({}) assert.deepEqual( apply, { n: new Counter(10), m: 10 } ) doc1.increment("/","n", 5) mat = doc1.materialize("/") assert.deepEqual( mat, { n: new Counter(15), m: 10 } ) doc2.merge(doc1) apply = doc2.applyPatches(apply) assert.deepEqual( apply, { n: new Counter(15), m: 10 } ) }) it('text can be managed as an array or a string', () => { const doc1 = create("aaaa") doc1.enablePatches(true) doc1.putObject("/", "notes", "hello world") let mat = doc1.materialize("/") assert.deepEqual( mat, { notes: "hello world" } ) const doc2 = create() let apply : any = doc2.materialize("/") doc2.enablePatches(true) apply = doc2.applyPatches(apply) doc2.merge(doc1); apply = doc2.applyPatches(apply) assert.deepEqual(_obj(apply), "_root") assert.deepEqual( apply, { notes: "hello world" } ) doc2.splice("/notes", 6, 5, "everyone"); apply = doc2.applyPatches(apply) assert.deepEqual( apply, { notes: "hello everyone" } ) mat = doc2.materialize("/") assert.deepEqual(_obj(mat), "_root") // @ts-ignore assert.deepEqual( mat, { notes: "hello everyone" } ) }) it('should set the OBJECT_ID property on lists, maps, and text objects and not on scalars', () => { const doc1 = create('aaaa') const mat: any = doc1.materialize("/") doc1.enablePatches(true) doc1.registerDatatype("counter", (n: number) => new Counter(n)) doc1.put("/", "string", "string", "str") doc1.put("/", "uint", 2, "uint") doc1.put("/", "int", 2, "int") doc1.put("/", "float", 2.3, "f64") doc1.put("/", "bytes", new Uint8Array(), "bytes") doc1.put("/", "counter", 1, "counter") doc1.put("/", "date", new Date(), "timestamp") doc1.putObject("/", "text", "text") doc1.putObject("/", "list", []) doc1.putObject("/", "map", {}) const applied = doc1.applyPatches(mat) assert.equal(_obj(applied.string), null) assert.equal(_obj(applied.uint), null) assert.equal(_obj(applied.int), null) assert.equal(_obj(applied.float), null) assert.equal(_obj(applied.bytes), null) assert.equal(_obj(applied.counter), null) assert.equal(_obj(applied.date), null) assert.equal(_obj(applied.text), null) assert.notEqual(_obj(applied.list), null) assert.notEqual(_obj(applied.map), null) }) it('should set the root OBJECT_ID to "_root"', () => { const doc1 = create('aaaa') const mat: any = doc1.materialize("/") assert.equal(_obj(mat), "_root") doc1.enablePatches(true) doc1.put("/", "key", "value") const applied = doc1.applyPatches(mat) assert.equal(_obj(applied), "_root") }) it.skip('it can patch quickly', () => { /* console.time("init") let doc1 = create() doc1.enablePatches(true) doc1.putObject("/", "notes", ""); let mat = doc1.materialize("/") let doc2 = doc1.fork() let testData = new Array( 100000 ).join("x") console.timeEnd("init") console.time("splice") doc2.splice("/notes", 0, 0, testData); console.timeEnd("splice") console.time("merge") doc1.merge(doc2) console.timeEnd("merge") console.time("patch") mat = doc1.applyPatches(mat) console.timeEnd("patch") */ }) }) }) // TODO: squash puts & deletes