Skip to content

Commit 89f6ad2

Browse files
authored
Merge pull request #7700 from nextcloud/test/yjs_pending_structs
test: detect out of sync state and recovery via yjs pendingStructs
2 parents 9c32faf + ca30ea1 commit 89f6ad2

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// 10 steps each inserting one character ("abcdefghij"), alternating between two clients
2+
export default [
3+
["AAI+AgPir\/+VDAAoAAAAA2RpcgF3A2x0cgcAAAAGBADir\/+VDAEBYQEAAAcBB2RlZmF1bHQDCXBhcmFncmFwaAA="],
4+
["AAISAQGa2Iz7AwCE4q\/\/lQwCAWIA"],
5+
["AAISAQHir\/+VDAOEmtiM+wMAAWMA"],
6+
["AAISAQGa2Iz7AwGE4q\/\/lQwDAWQA"],
7+
["AAISAQHir\/+VDASEmtiM+wMBAWUA"],
8+
["AAISAQGa2Iz7AwKE4q\/\/lQwEAWYA"],
9+
["AAISAQHir\/+VDAWEmtiM+wMCAWcA"],
10+
["AAISAQGa2Iz7AwOE4q\/\/lQwFAWgA"],
11+
["AAISAQHir\/+VDAaEmtiM+wMDAWkA"],
12+
["AAISAQGa2Iz7AwSE4q\/\/lQwGAWoA"],
13+
]

src/tests/yjs.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import { Collaboration } from '@tiptap/extension-collaboration'
67
import * as decoding from 'lib0/decoding'
78
import * as encoding from 'lib0/encoding'
89
import * as syncProtocol from 'y-protocols/sync'
910
import { Doc, encodeStateAsUpdate } from 'yjs'
11+
import { createRichEditor } from '../EditorFactory.js'
12+
import { createMarkdownSerializer } from '../extensions/Markdown.js'
1013
import { decodeArrayBuffer } from '../helpers/base64.ts'
1114
import recorded from './fixtures/recorded.js'
15+
import stepsTwoClients from './fixtures/steps_two_clients.js'
1216

1317
describe('recorded session', () => {
1418
const flattened = recorded.flat()
@@ -139,4 +143,44 @@ describe('recorded session', () => {
139143
expect(replies.length).toBe(0)
140144
expect(size(replies)).toBe(0)
141145
})
146+
147+
test('detecting out of sync due to missing step via pending structs', () => {
148+
// 10 steps each inserting one character, alternating between two clients
149+
const syncSteps = stepsTwoClients.flat()
150+
const fullDocumentContent = 'abcdefghij'
151+
// Remove steps 2-5 from syncSteps and store them in missingSteps
152+
const missingSteps = syncSteps.splice(1, 4)
153+
154+
// Create editor to access the content of the ydoc
155+
const ydoc = new Doc()
156+
const tiptap = createRichEditor({
157+
extensions: [Collaboration.configure({ document: ydoc })],
158+
})
159+
const serializer = createMarkdownSerializer(tiptap.schema)
160+
161+
// Apply steps 1 and 6-10 (steps 2-5 are missing)
162+
// Pending structs detected and only first charcter visible in content
163+
syncSteps.map((step) => processStep(ydoc, step))
164+
expect(ydoc.store.pendingStructs).not.toBeNull()
165+
expect(serializer.serialize(tiptap.state.doc)).toEqual(
166+
fullDocumentContent.slice(0, 1),
167+
)
168+
169+
// Apply missing steps 2-5
170+
for (const [i, step] of missingSteps.entries()) {
171+
processStep(ydoc, step)
172+
if (i < missingSteps.length - 1) {
173+
// Each step except the last, one more character gets visible in content
174+
expect(ydoc.store.pendingStructs).not.toBeNull()
175+
expect(serializer.serialize(tiptap.state.doc)).toEqual(
176+
fullDocumentContent.slice(0, i + 2),
177+
)
178+
}
179+
}
180+
181+
// After all missing steps got applied, content is complete and no pending structs detected anymore
182+
expect(ydoc.store.pendingStructs).toBeNull()
183+
expect(serializer.serialize(tiptap.state.doc)).toEqual(fullDocumentContent)
184+
tiptap.destroy()
185+
})
142186
})

0 commit comments

Comments
 (0)