Files
yjs/tests/attribution.tests.js

175 lines
6.9 KiB
JavaScript
Raw Normal View History

2025-05-21 16:04:55 +02:00
/**
* Testing if encoding/decoding compatibility and integration compatibility is given.
* We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches.
*
* The v1 documents were generated with Yjs v13.2.0 based on the randomisized tests.
*/
import * as Y from '../src/index.js'
import * as t from 'lib0/testing'
2025-10-20 02:14:02 +02:00
import * as delta from 'lib0/delta'
2026-01-10 22:04:30 +01:00
import { init } from './testHelper.js' // eslint-disable-line
2025-05-21 16:04:55 +02:00
2025-05-29 21:47:29 +02:00
/**
* @param {t.TestCase} _tc
*/
export const testRelativePositions = _tc => {
const ydoc = new Y.Doc()
2025-12-29 11:37:01 +01:00
const ytext = ydoc.get()
2025-05-29 21:47:29 +02:00
ytext.insert(0, 'hello world')
const v1 = Y.cloneDoc(ydoc)
ytext.delete(1, 6)
2025-06-05 14:52:55 +02:00
ytext.insert(1, 'x')
2025-05-29 21:47:29 +02:00
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const rel = Y.createRelativePositionFromTypeIndex(ytext, 9, 1, am) // pos after "hello wo"
const abs1 = Y.createAbsolutePositionFromRelativePosition(rel, ydoc, true, am)
const abs2 = Y.createAbsolutePositionFromRelativePosition(rel, ydoc, true)
t.assert(abs1?.index === 9)
t.assert(abs2?.index === 3)
}
2025-05-21 16:04:55 +02:00
/**
* @param {t.TestCase} _tc
*/
export const testAttributedEvents = _tc => {
const ydoc = new Y.Doc()
2025-12-29 11:37:01 +01:00
const ytext = ydoc.get()
2025-05-21 16:04:55 +02:00
ytext.insert(0, 'hello world')
const v1 = Y.cloneDoc(ydoc)
ydoc.transact(() => {
ytext.delete(6, 5)
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
2026-01-10 17:32:29 +01:00
const c1 = ytext.toDelta(am)
2025-12-24 09:18:38 +01:00
t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] }))
2025-05-21 16:04:55 +02:00
let calledObserver = false
ytext.observe(event => {
const d = event.getDelta(am)
2025-12-24 09:18:38 +01:00
t.compare(d, delta.create().retain(11).insert('!', null, { insert: [] }))
2025-05-21 16:04:55 +02:00
calledObserver = true
})
2026-01-10 00:57:43 +01:00
ytext.applyDelta(delta.create().retain(11).insert('!'), am)
2025-05-21 16:04:55 +02:00
t.assert(calledObserver)
}
/**
* @param {t.TestCase} _tc
*/
export const testInsertionsMindingAttributedContent = _tc => {
const ydoc = new Y.Doc()
2025-12-29 11:37:01 +01:00
const ytext = ydoc.get()
ytext.insert(0, 'hello world')
const v1 = Y.cloneDoc(ydoc)
ydoc.transact(() => {
ytext.delete(6, 5)
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
2026-01-10 17:32:29 +01:00
const c1 = ytext.toDelta(am)
2025-12-24 09:18:38 +01:00
t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] }))
ytext.applyDelta(delta.create().retain(11).insert('content'), am)
t.assert(ytext.toString() === 'hello content')
}
/**
* @param {t.TestCase} _tc
*/
export const testInsertionsIntoAttributedContent = _tc => {
const ydoc = new Y.Doc()
2025-12-29 11:37:01 +01:00
const ytext = ydoc.get()
ytext.insert(0, 'hello ')
const v1 = Y.cloneDoc(ydoc)
ydoc.transact(() => {
ytext.insert(6, 'word')
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
2026-01-10 17:32:29 +01:00
const c1 = ytext.toDelta(am)
2025-12-24 09:18:38 +01:00
t.compare(c1, delta.create().insert('hello ').insert('word', null, { insert: [] }))
ytext.applyDelta(delta.create().retain(9).insert('l'), am)
t.assert(ytext.toString() === 'hello world')
}
2025-11-27 19:59:26 +01:00
export const testYdocDiff = () => {
const ydocStart = new Y.Doc()
2025-12-29 11:37:01 +01:00
ydocStart.get('text').insert(0, 'hello')
ydocStart.get('array').insert(0, [1, 2, 3])
ydocStart.get('map').setAttr('k', 42)
ydocStart.get('map').setAttr('nested', new Y.Type())
2025-11-27 19:59:26 +01:00
const ydocUpdated = Y.cloneDoc(ydocStart)
2025-12-29 11:37:01 +01:00
ydocUpdated.get('text').insert(5, ' world')
ydocUpdated.get('array').insert(1, ['x'])
ydocUpdated.get('map').setAttr('newk', 42)
ydocUpdated.get('map').getAttr('nested').insert(0, [1])
2025-11-27 19:59:26 +01:00
// @todo add custom attribution
2026-02-23 20:22:40 +01:00
const d = Y.diffDocsToDelta(ydocStart, ydocUpdated).done()
console.log('calculated diff', d.toJSON())
2026-02-23 20:22:40 +01:00
const expected = delta.create()
2025-12-24 09:18:38 +01:00
.modifyAttr('text', delta.create().retain(5).insert(' world', null, { insert: [] }))
.modifyAttr('array', delta.create().retain(1).insert(['x'], null, { insert: [] }))
.modifyAttr('map', delta.create().setAttr('newk', 42, { insert: [] }).modifyAttr('nested', delta.create().insert([1], null, { insert: [] })))
2026-02-23 20:22:40 +01:00
const expectedDone = expected.done()
t.compare(d, expectedDone)
2025-11-27 19:59:26 +01:00
}
2025-12-07 02:13:04 +01:00
export const testChildListContent = () => {
const ydocStart = new Y.Doc()
const ydocUpdated = Y.cloneDoc(ydocStart)
2025-12-29 11:37:01 +01:00
const yf = new Y.Type('test')
2025-12-07 02:13:04 +01:00
let calledEvent = 0
2025-12-24 09:18:38 +01:00
yf.applyDelta(delta.create().insert('test content').setAttr('k', 'v'))
2025-12-07 02:13:04 +01:00
2025-12-29 11:37:01 +01:00
const yarray = ydocUpdated.get('array')
yarray.observeDeep(event => {
2025-12-07 02:13:04 +01:00
calledEvent++
const d = event.deltaDeep
2025-12-24 09:18:38 +01:00
const expectedD = delta.create().insert([delta.create('test').insert('test content').setAttr('k', 'v')])
2025-12-07 02:13:04 +01:00
t.compare(d, expectedD)
})
2025-12-29 11:37:01 +01:00
ydocUpdated.get('array').insert(0, [yf])
2025-12-07 02:13:04 +01:00
t.assert(calledEvent === 1)
const d = Y.diffDocsToDelta(ydocStart, ydocUpdated)
console.log('calculated diff', d.toJSON())
2025-12-24 09:18:38 +01:00
const expected = delta.create()
.modifyAttr('array', delta.create().insert([delta.create('test').insert('test content', null, { insert: [] }).setAttr('k', 'v', { insert: [] })], null, { insert: [] }))
t.compare(d.done(), expected.done())
2025-12-07 02:13:04 +01:00
}
2026-01-10 22:04:30 +01:00
/**
* @param {t.TestCase} tc
*/
export const testAttributionSession1 = tc => {
const { testConnector, users, text0, text1 } = init(tc, { users: 3 })
users[0].gc = false
2026-01-10 22:04:30 +01:00
const globalAttributions = new Y.Attributions()
const v1 = Y.cloneDoc(users[0])
users.forEach(user => user.on('update', (update, _, ydoc, tr) => {
if (!tr.local) return
const userid = ydoc.clientID.toString()
const contentIds = Y.createContentIdsFromUpdate(update)
2026-01-10 22:04:30 +01:00
Y.insertIntoIdMap(globalAttributions.inserts, Y.createIdMapFromIdSet(contentIds.inserts, [Y.createContentAttribute('insert', userid)]))
Y.insertIntoIdMap(globalAttributions.deletes, Y.createIdMapFromIdSet(contentIds.deletes, [Y.createContentAttribute('delete', userid)]))
}))
text0.insert(0, 'a')
text1.insert(0, 'b')
testConnector.flushAllMessages()
const d1 = text0.toDelta(Y.createAttributionManagerFromDiff(v1, users[0], { attrs: globalAttributions }))
t.compare(d1, delta.create().insert('a', null, { insert: ['0'] }).insert('b', null, { insert: ['1'] }))
const v2 = Y.cloneDoc(users[0])
text0.delete(1, 1)
text1.insert(2, 'c')
2026-01-10 22:04:30 +01:00
testConnector.flushAllMessages()
const d2 = text0.toDelta(Y.createAttributionManagerFromDiff(v2, users[0], { attrs: globalAttributions }))
t.compare(d2, delta.create().insert('a').insert('b', null, { delete: ['0'] }).insert('c', null, { insert: ['1'] }))
const onlyUser0ChangesAttributed = {
inserts: Y.filterIdMap(globalAttributions.inserts, attrs => attrs.some(attr => attr.name === 'insert' && attr.val === '0')),
deletes: Y.filterIdMap(globalAttributions.deletes, attrs => attrs.some(attr => attr.name === 'delete' && attr.val === '0'))
}
const amUser0 = new Y.TwosetAttributionManager(onlyUser0ChangesAttributed.inserts, onlyUser0ChangesAttributed.deletes)
const d3 = text0.toDelta(amUser0)
t.compare(d3, delta.create().insert('a', null, { insert: ['0'] }).insert('b', null, { delete: ['0'] }).insert('c'))
Y.undoContentIds(users[0], Y.createContentIdsFromContentMap(onlyUser0ChangesAttributed))
const d4 = text0.toDelta()
t.compare(d4, delta.create().insert('bc'))
2026-01-10 22:04:30 +01:00
}