fix: root level children update

This commit is contained in:
Eric Maciel 2021-01-20 20:28:52 -05:00
parent a887e26391
commit b5bd7fe750
5 changed files with 100 additions and 22 deletions

View File

@ -5,7 +5,6 @@ interface TestDoc {
status: string
}
// TODO: delete this?
describe('old state error replication', () => {
const clientDocSet = new Automerge.DocSet()
const serverDocSet = new Automerge.DocSet()

View File

@ -23,11 +23,36 @@ const opSet = (op: Automerge.Diff, [map, ops]: any, doc: any) => {
const { link, value, path, obj, key } = op
try {
// no slate op needed for root key cursor updates
// We can ignore any root level cursor updates since those
// will not correspond to any slate operations
if (obj === rootKey && key === 'cursors') {
return [map, ops]
}
// Handle updates received for the root children array
if (obj === rootKey && key === 'children' && map[value]) {
// First remove all existing child nodes
for (let i = doc.children.length - 1; i >= 0; i--) {
ops.push((map: any) => ({
type: 'remove_node',
path: [i],
node: doc.children[i]
}))
}
// Then add all the newly defined nodes
const newChildren: Node[] = map[value]
newChildren.forEach((child, index) => {
ops.push((map: any) => ({
type: 'insert_node',
path: [index],
node: child
}))
})
return [map, ops]
}
if (path && path[0] !== 'cursors') {
ops.push(setDataOp(op, doc))
} else if (map[obj]) {

View File

@ -137,19 +137,30 @@ export const AutomergeConnector = {
e.handleError(err, {
type: 'applyOperation - toSlateOp',
operations,
current: Automerge.save(current)
current: Automerge.save(current),
updated: Automerge.save(updated)
})
}
e.isRemote = true
Editor.withoutNormalizing(e, () => {
if (HistoryEditor.isHistoryEditor(e) && !preserveExternalHistory) {
HistoryEditor.withoutSaving(e, () => {
try {
if (HistoryEditor.isHistoryEditor(e) && !preserveExternalHistory) {
HistoryEditor.withoutSaving(e, () => {
slateOps.forEach((o: Operation) => e.apply(o))
})
} else {
slateOps.forEach((o: Operation) => e.apply(o))
}
} catch (err) {
e.handleError(err, {
type: 'applyOperation - slateOps apply',
operations,
slateOps,
current: Automerge.save(current),
updated: Automerge.save(updated)
})
} else {
slateOps.forEach((o: Operation) => e.apply(o))
}
e.onCursor && e.onCursor(updated.cursors)
@ -180,7 +191,7 @@ export const AutomergeConnector = {
if (!doc) return
const changed = Automerge.change<SyncDoc>(doc, (d: any) => {
delete d.cursors
d.cursors = {}
})
e.docSet.setDoc(docId, changed as any)

View File

@ -1,4 +1,4 @@
import Automerge, { Frontend } from 'automerge'
import Automerge from 'automerge'
import { createServer } from 'http'
import fs from 'fs'
import isEqual from 'lodash/isEqual'
@ -60,9 +60,17 @@ describe('automerge editor client tests', () => {
})
const createCollabEditor = async (
editorOptions: AutomergeOptions & SocketIOPluginOptions = options
editorOptions?: Partial<AutomergeOptions> & Partial<SocketIOPluginOptions>
) => {
const editor = withIOCollaboration(createEditor(), editorOptions)
// Given a docId we an generate the collab url
if (editorOptions?.docId) {
editorOptions.url = `http://localhost:5000${editorOptions?.docId}`
}
const editor = withIOCollaboration(createEditor(), {
...options,
...editorOptions
})
const oldReceiveDocument = editor.receiveDocument
const promise = new Promise<void>(resolve => {
@ -188,8 +196,8 @@ describe('automerge editor client tests', () => {
editor2.destroy()
})
it('deep nested tree error', () => {
// Ready from our test json file for the deep tree error
it('should not throw deep nested tree error', () => {
// Read from our test json file for the deep tree error
// This allows us to easily reproduce real production errors
// and create test cases that resolve those errors
const rawData = fs.readFileSync(
@ -205,6 +213,26 @@ describe('automerge editor client tests', () => {
toSlateOp(operations, currentDoc)
})
it('should update children for a root level children operation', async () => {
const editor = await createCollabEditor()
const oldDoc = collabBackend.backend.documentSetMap[docId].getDoc(docId)
const newDoc = Automerge.change(oldDoc, changed => {
// @ts-ignore
changed.children = [
{ type: 'paragraph', children: [{ text: 'new' }] },
{ type: 'paragraph', children: [{ text: 'nodes' }] }
]
})
collabBackend.backend.documentSetMap[docId].setDoc(docId, newDoc)
await waitForCondition(() => editor.children.length === 2)
expect(editor.children.length).toEqual(2)
expect(Node.string(editor.children[0])).toEqual('new')
expect(Node.string(editor.children[1])).toEqual('nodes')
})
afterAll(() => {
collabBackend.destroy()
server.close()

View File

@ -16,19 +16,34 @@ const useCursor = (
useEffect(() => {
e.onCursor = (data: Cursors) => {
if (!mountedRef.current) return
const ranges: Cursor[] = []
const cursors = toJS(data)
for (let cursor in cursors) {
if (cursor !== e.clientId && cursors[cursor]) {
ranges.push(JSON.parse(cursors[cursor]))
}
// If the cursor data is null or undefined, unset all active cursors
if (!data) {
setCursorData(ranges)
return
}
// only update state if this component is still mounted
if (mountedRef.current) {
setCursorData(ranges)
try {
const cursors = toJS(data)
for (let cursor in cursors) {
if (cursor !== e.clientId && cursors[cursor]) {
ranges.push(JSON.parse(cursors[cursor]))
}
}
// only update state if this component is still mounted
if (mountedRef.current) {
setCursorData(ranges)
}
} catch (err) {
e.handleError(err, {
type: 'onCursor',
data,
ranges
})
}
}
}, [])