Thanks @kjohnson,
that helped me to figure out the issue.
First note that ydoc.getMap('content').toJSON()
is the same for all clients.
I noticed that yi
has two deleted formatting attributes while yj
does not. This is why the updates don’t have the same length. Here is some background about formatting:
Internally, Yjs manages formatting as markers in the text: {bold:true}some text{ bold: null }
. Assume User1 deletes "some"
and User2 deletes "text"
(resulting in two different document updates). When we apply both document updates at User3, User3 creates a third document update that deletes the formatting attributes (because the inner content is now deleted). This document update must also be applied at other clients.
I modified your test and listened to these events:
/**
* There is some custom encoding/decoding happening in PermanentUserData.
* This is why it landed here.
*
* @param {t.TestCase} tc
*/
export const testForumIssue = async tc => {
const stateOfFirstDoc = Uint8Array.from(
[1, 12, 179, 149, 176, 177, 9, 0, 39, 1, 7, 99, 111, 110, 116, 101, 110, 116,
8, 100, 111, 99, 117, 109, 101, 110, 116, 0, 7, 0, 179, 149, 176, 177, 9, 0,
1, 39, 0, 179, 149, 176, 177, 9, 1, 8, 99, 104, 105, 108, 100, 114, 101, 110,
0, 7, 0, 179, 149, 176, 177, 9, 2, 1, 39, 0, 179, 149, 176, 177, 9, 3, 4,
116, 101, 120, 116, 2, 4, 0, 179, 149, 176, 177, 9, 4, 1, 103, 129, 179, 149,
176, 177, 9, 5, 7, 132, 179, 149, 176, 177, 9, 12, 2, 101, 108, 40, 0, 179,
149, 176, 177, 9, 3, 6, 111, 98, 106, 101, 99, 116, 1, 119, 4, 116, 101, 120,
116, 40, 0, 179, 149, 176, 177, 9, 1, 6, 111, 98, 106, 101, 99, 116, 1, 119,
5, 98, 108, 111, 99, 107, 40, 0, 179, 149, 176, 177, 9, 1, 4, 116, 121, 112,
101, 1, 119, 4, 108, 105, 110, 101, 40, 0, 179, 149, 176, 177, 9, 1, 4, 100,
97, 116, 97, 1, 118, 0, 1, 179, 149, 176, 177, 9, 1, 6, 7])
const updateForFirstDoc = Uint8Array.from(
[1, 2, 234, 166, 220, 135, 7, 0, 198, 179, 149, 176, 177, 9, 8, 179, 149, 176,
177, 9, 9, 6, 115, 116, 114, 111, 110, 103, 6, 34, 116, 114, 117, 101, 34,
198, 179, 149, 176, 177, 9, 10, 179, 149, 176, 177, 9, 11, 6, 115, 116, 114,
111, 110, 103, 4, 110, 117, 108, 108, 0])
const stateOfSecondDoc = Uint8Array.from(
[2, 12, 179, 149, 176, 177, 9, 0, 39, 1, 7, 99, 111, 110, 116, 101, 110, 116,
8, 100, 111, 99, 117, 109, 101, 110, 116, 0, 7, 0, 179, 149, 176, 177, 9, 0,
1, 39, 0, 179, 149, 176, 177, 9, 1, 8, 99, 104, 105, 108, 100, 114, 101, 110,
0, 7, 0, 179, 149, 176, 177, 9, 2, 1, 39, 0, 179, 149, 176, 177, 9, 3, 4,
116, 101, 120, 116, 2, 4, 0, 179, 149, 176, 177, 9, 4, 4, 103, 111, 108, 102,
132, 179, 149, 176, 177, 9, 8, 2, 32, 104, 132, 179, 149, 176, 177, 9, 10, 4,
111, 116, 101, 108, 40, 0, 179, 149, 176, 177, 9, 3, 6, 111, 98, 106, 101,
99, 116, 1, 119, 4, 116, 101, 120, 116, 40, 0, 179, 149, 176, 177, 9, 1, 6,
111, 98, 106, 101, 99, 116, 1, 119, 5, 98, 108, 111, 99, 107, 40, 0, 179,
149, 176, 177, 9, 1, 4, 116, 121, 112, 101, 1, 119, 4, 108, 105, 110, 101,
40, 0, 179, 149, 176, 177, 9, 1, 4, 100, 97, 116, 97, 1, 118, 0, 2, 234, 166,
220, 135, 7, 0, 198, 179, 149, 176, 177, 9, 8, 179, 149, 176, 177, 9, 9, 6,
115, 116, 114, 111, 110, 103, 6, 34, 116, 114, 117, 101, 34, 198, 179, 149,
176, 177, 9, 10, 179, 149, 176, 177, 9, 11, 6, 115, 116, 114, 111, 110, 103,
4, 110, 117, 108, 108, 0])
const updateForSecondDoc = Uint8Array.from(
[0, 1, 179, 149, 176, 177, 9, 1, 6, 7])
const yi = new Y.Doc()
const yj = new Y.Doc()
yi.on('update', (update, origin) => {
console.log('yi updated')
if (origin !== 'test') {
console.log('yi created an extra update that must be sent to other clients')
Y.applyUpdate(yj, update, 'test')
}
})
yj.on('update', (update, origin) => {
console.log('yj updated')
if (origin !== 'test') {
console.log('yj created an extra update that must be sent to other clients')
Y.applyUpdate(yi, update, 'test')
}
})
Y.applyUpdate(yi, stateOfFirstDoc, 'test')
Y.applyUpdate(yi, updateForFirstDoc, 'test')
Y.applyUpdate(yj, stateOfSecondDoc, 'test')
Y.applyUpdate(yj, updateForSecondDoc, 'test')
t.compare(yj.getMap('content').toJSON(), yi.getMap('content').toJSON())
t.compare(Y.encodeStateVector(yi), (Y.encodeStateVector(yj)))
t.compare(Y.encodeStateAsUpdate(yi), Y.encodeStateAsUpdate(yj))
}
The above test case doesn’t fail. You can add it to yjs/tests/y-text.tests.js
and run npm run debug
, or convert it back to mocha.
The output of this test is:
yi updated
tests.js:15418 yi updated
tests.js:15418 yi updated
tests.js:15420 yi created an extra update that must be sent to other clients
tests.js:15426 yj updated
tests.js:15426 yj updated
It shows that there is an update that you don’t send. Once you apply the “extra update”, your test case will work as well 