diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 5f2aaa8c..4a1746ac 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -284,6 +284,9 @@ const cleanupTransactions = (transactionCleanups, i) => { .forEach(event => { event.currentTarget = type }) + // sort events by path length so that top-level events are fired first. + events + .sort((event1, event2) => event1.path.length - event2.path.length) // We don't need to check for events.length // because we know it has at least one element callEventHandlerListeners(type._dEH, events, transaction) diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 724b50fc..5b0827c2 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -204,6 +204,34 @@ export const testInsertAndDeleteEventsForTypes = tc => { compare(users) } +/** + * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 + * + * Deep observers generate multiple events. When an array added at item at, say, position 0, + * and item 1 changed then the array-add event should fire first so that the change event + * path is correct. A array binding might lead to an inconsistent state otherwise. + * + * @param {t.TestCase} tc + */ +export const testObserveDeepEventOrder = tc => { + const { array0, users } = init(tc, { users: 2 }) + /** + * @type {Array} + */ + let events = [] + array0.observeDeep(e => { + events = e + }) + array0.insert(0, [new Y.Map()]) + users[0].transact(() => { + array0.get(0).set('a', 'a') + array0.insert(0, [0]) + }) + for (let i = 1; i < events.length; i++) { + t.assert(events[i - 1].path.length <= events[i].path.length, 'path size increases, fire top-level events first') + } +} + /** * @param {t.TestCase} tc */