diff --git a/src/structs/Item.js b/src/structs/Item.js index 2d2b1bb9..77510cb6 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -223,6 +223,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo } else { left = parentType._map.get(item.parentSub) || null } + // drop cross-parent left so origin doesn't mislead the remote (#757) + if (left !== null && left.parent !== parentType) { + left = null + } } const nextClock = getState(store, ownClientID) const nextId = createID(ownClientID, nextClock) diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index faf42aff..e936f468 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -811,3 +811,35 @@ export const testUndoDoingStackItem = async (_tc) => { t.compare(metaRedo, '42', 'currStackItem is accessible while redoing') t.compare(undoManager.currStackItem, null, 'currStackItem is null after observe/transaction') } + +/** + * @see https://github.com/yjs/yjs/issues/757 + * @param {t.TestCase} _tc + */ +export const testUndoSetAttributeAndDeleteSyncsAttributes = _tc => { + const doc = new Y.Doc() + const root = /** @type {Y.XmlText} */ (doc.get('sharedRoot', Y.XmlText)) + const button = new Y.XmlText() + button.setAttribute('type', 'button') + button.setAttribute('test', true) + button.insert(0, 'Click me') + root.insertEmbed(0, button) + + const undoManager = new Y.UndoManager(root) + undoManager.stopCapturing() + + button.setAttribute('type', 'paragraph') + root.delete(0, 1) + undoManager.undo() + + const expectedAttrs = { type: 'button', test: true } + const expectedText = 'Click me' + t.compare(root.toDelta()[0].insert.getAttributes(), expectedAttrs) + t.compare(root.toDelta()[0].insert.toString(), expectedText) + + const remoteDoc = new Y.Doc() + Y.applyUpdateV2(remoteDoc, Y.encodeStateAsUpdateV2(doc)) + const remoteRoot = /** @type {Y.XmlText} */ (remoteDoc.get('sharedRoot', Y.XmlText)) + t.compare(remoteRoot.toDelta()[0].insert.getAttributes(), expectedAttrs) + t.compare(remoteRoot.toDelta()[0].insert.toString(), expectedText) +}