From 6afd5a38ba1c54aeda605b49d1543af0983c30d3 Mon Sep 17 00:00:00 2001 From: Martin Kleppmann Date: Mon, 8 Feb 2021 18:33:22 +0000 Subject: [PATCH] Pass a path to the observed object to observable callback --- @types/automerge/index.d.ts | 5 +++-- frontend/observable.js | 11 +++++++---- test/observable_test.js | 20 ++++++++++---------- test/typescript_test.ts | 3 ++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/@types/automerge/index.d.ts b/@types/automerge/index.d.ts index 08975255a..5b353d82f 100644 --- a/@types/automerge/index.d.ts +++ b/@types/automerge/index.d.ts @@ -30,8 +30,9 @@ declare module 'automerge' { observable?: Observable } - type PatchCallback = (patch: Patch, before: T, after: T, local: boolean, changes: Uint8Array[]) => void - type ObserverCallback = (diff: ObjectDiff, before: T, after: T, local: boolean, changes: Uint8Array[]) => void + type JSONPath = (string | number)[] + type PatchCallback = (patch: Patch, before: T, after: T, local: boolean, changes: Uint8Array[], path: JSONPath) => void + type ObserverCallback = (diff: ObjectDiff, before: T, after: T, local: boolean, changes: Uint8Array[], path: JSONPath) => void class Observable { observe(object: T, callback: ObserverCallback): void diff --git a/frontend/observable.js b/frontend/observable.js index e90911d40..9d85ab605 100644 --- a/frontend/observable.js +++ b/frontend/observable.js @@ -20,18 +20,18 @@ class Observable { * changes that were applied to the document (as Uint8Arrays). */ patchCallback(patch, before, after, local, changes) { - this._objectUpdate(patch.diffs, before, after, local, changes) + this._objectUpdate(patch.diffs, before, after, local, changes, []) } /** * Recursively walks a patch and calls the callbacks for all objects that * appear in the patch. */ - _objectUpdate(diff, before, after, local, changes) { + _objectUpdate(diff, before, after, local, changes, path) { if (!diff.objectId) return if (this.observers[diff.objectId]) { for (let callback of this.observers[diff.objectId]) { - callback(diff, before, after, local, changes) + callback(diff, before, after, local, changes, path) } } @@ -39,6 +39,7 @@ class Observable { for (let propName of Object.keys(diff.props)) { for (let opId of Object.keys(diff.props[propName])) { let childDiff = diff.props[propName][opId], childBefore, childAfter + let pathElem = propName if (diff.type === 'map') { childBefore = before && before[CONFLICTS] && before[CONFLICTS][propName] && @@ -59,6 +60,7 @@ class Observable { } childAfter = after && after[CONFLICTS] && after[CONFLICTS][index] && after[CONFLICTS][index][opId] + pathElem = index } else if (diff.type === 'text') { const index = parseInt(propName) @@ -67,9 +69,10 @@ class Observable { childBefore = before && before.get(index) } childAfter = after && after.get(index) + pathElem = index } - this._objectUpdate(childDiff, childBefore, childAfter, local, changes) + this._objectUpdate(childDiff, childBefore, childAfter, local, changes, path.concat([pathElem])) } } } diff --git a/test/observable_test.js b/test/observable_test.js index 2aa7db249..f6434b3eb 100644 --- a/test/observable_test.js +++ b/test/observable_test.js @@ -5,7 +5,7 @@ describe('Automerge.Observable', () => { it('allows registering a callback on the root object', () => { let observable = new Automerge.Observable(), callbackChanges let doc = Automerge.init({observable}), actor = Automerge.getActorId(doc) - observable.observe(doc, (diff, before, after, local, changes) => { + observable.observe(doc, (diff, before, after, local, changes, path) => { callbackChanges = changes assert.deepStrictEqual(diff, { objectId: '_root', type: 'map', props: {bird: {[`1@${actor}`]: {value: 'Goldfinch'}}} @@ -14,6 +14,7 @@ describe('Automerge.Observable', () => { assert.deepStrictEqual(after, {bird: 'Goldfinch'}) assert.strictEqual(local, true) assert.strictEqual(changes.length, 1) + assert.deepStrictEqual(path, []) }) doc = Automerge.change(doc, doc => doc.bird = 'Goldfinch') assert.strictEqual(callbackChanges.length, 1) @@ -25,7 +26,7 @@ describe('Automerge.Observable', () => { let observable = new Automerge.Observable(), callbackCalled = false let doc = Automerge.from({text: new Automerge.Text()}, {observable}) let actor = Automerge.getActorId(doc) - observable.observe(doc.text, (diff, before, after, local) => { + observable.observe(doc.text, (diff, before, after, local, changes, path) => { callbackCalled = true assert.deepStrictEqual(diff, { objectId: `1@${actor}`, type: 'text', edits: [ @@ -41,6 +42,7 @@ describe('Automerge.Observable', () => { assert.deepStrictEqual(before.toString(), '') assert.deepStrictEqual(after.toString(), 'abc') assert.deepStrictEqual(local, true) + assert.deepStrictEqual(path, ['text']) }) doc = Automerge.change(doc, doc => doc.text.insertAt(0, 'a', 'b', 'c')) assert.strictEqual(callbackCalled, true) @@ -73,7 +75,7 @@ describe('Automerge.Observable', () => { let observable = new Automerge.Observable(), callbackCalled = false let doc = Automerge.from({todos: [{title: 'Buy milk', done: false}]}, {observable}) const actor = Automerge.getActorId(doc) - observable.observe(doc.todos[0], (diff, before, after, local) => { + observable.observe(doc.todos[0], (diff, before, after, local, changes, path) => { callbackCalled = true assert.deepStrictEqual(diff, { objectId: `2@${actor}`, type: 'map', props: {done: {[`5@${actor}`]: {value: true}}} @@ -81,6 +83,7 @@ describe('Automerge.Observable', () => { assert.deepStrictEqual(before, {title: 'Buy milk', done: false}) assert.deepStrictEqual(after, {title: 'Buy milk', done: true}) assert.strictEqual(local, true) + assert.deepStrictEqual(path, ['todos', 0]) }) doc = Automerge.change(doc, doc => doc.todos[0].done = true) assert.strictEqual(callbackCalled, true) @@ -113,7 +116,7 @@ describe('Automerge.Observable', () => { doc.todos = new Automerge.Table() rowId = doc.todos.add({title: 'Buy milk', done: false}) }) - observable.observe(doc.todos.byId(rowId), (diff, before, after, local) => { + observable.observe(doc.todos.byId(rowId), (diff, before, after, local, changes, path) => { callbackCalled = true assert.deepStrictEqual(diff, { objectId: `2@${actor}`, type: 'map', props: {done: {[`5@${actor}`]: {value: true}}} @@ -121,6 +124,7 @@ describe('Automerge.Observable', () => { assert.deepStrictEqual(before, {id: rowId, title: 'Buy milk', done: false}) assert.deepStrictEqual(after, {id: rowId, title: 'Buy milk', done: true}) assert.strictEqual(local, true) + assert.deepStrictEqual(path, ['todos', rowId]) }) doc = Automerge.change(doc, doc => doc.todos.byId(rowId).done = true) assert.strictEqual(callbackCalled, true) @@ -133,12 +137,7 @@ describe('Automerge.Observable', () => { doc.text = new Automerge.Text() doc.text.insertAt(0, 'a', 'b', {start: 'bold'}, 'c', {end: 'bold'}) }) - observable.observe(doc, (patch, before, after) => { - console.log(JSON.stringify(patch, null, 4)) - console.log('before =', before.text.elems) - console.log('after =', after.text.elems) - }) - observable.observe(doc.text.get(2), (diff, before, after, local) => { + observable.observe(doc.text.get(2), (diff, before, after, local, changes, path) => { callbackCalled = true assert.deepStrictEqual(diff, { objectId: `4@${actor}`, type: 'map', props: {start: {[`9@${actor}`]: {value: 'italic'}}} @@ -146,6 +145,7 @@ describe('Automerge.Observable', () => { assert.deepStrictEqual(before, {start: 'bold'}) assert.deepStrictEqual(after, {start: 'italic'}) assert.strictEqual(local, true) + assert.deepStrictEqual(path, ['text', 2]) }) doc = Automerge.change(doc, doc => doc.text.get(2).start = 'italic') assert.strictEqual(callbackCalled, true) diff --git a/test/typescript_test.ts b/test/typescript_test.ts index da61ad744..6e3b23319 100644 --- a/test/typescript_test.ts +++ b/test/typescript_test.ts @@ -583,7 +583,7 @@ describe('TypeScript support', () => { let observable = new Automerge.Observable(), callbackCalled = false let doc = Automerge.from({text: new Automerge.Text()}, {observable}) let actor = Automerge.getActorId(doc) - observable.observe(doc.text, (diff, before, after, local, changes) => { + observable.observe(doc.text, (diff, before, after, local, changes, path) => { callbackCalled = true assert.deepStrictEqual(diff.edits, [{action: 'insert', index: 0, elemId: `2@${actor}`}]) assert.deepStrictEqual(diff.props, {0: {[`2@${actor}`]: {value: 'a'}}}) @@ -592,6 +592,7 @@ describe('TypeScript support', () => { assert.strictEqual(local, true) assert.strictEqual(changes.length, 1) assert.ok(changes[0] instanceof Uint8Array) + assert.deepStrictEqual(path, ['text']) }) doc = Automerge.change(doc, doc => doc.text.insertAt(0, 'a')) assert.strictEqual(callbackCalled, true)