fixed most tests for delta v2 migration

This commit is contained in:
Kevin Jahns
2025-10-21 16:31:59 +02:00
parent 91384b54bf
commit c9829b0993
15 changed files with 1047 additions and 1471 deletions

View File

@@ -233,7 +233,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
/**
* Accumulate all (list) children of a type and return them as an Array.
*
* @param {AbstractType} t
* @param {import('../utils/types.js').YType} t
* @return {Array<Item>}
*/
export const getTypeChildren = t => {
@@ -272,7 +272,7 @@ export const callTypeObservers = (type, transaction, event) => {
/**
* Abstract Yjs Type class
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=delta.Delta<any,any,any,any,any>]
* @template {YType_} [Self=any]
* @template {AbstractType<any,any>} [Self=any]
*/
export class AbstractType {
constructor () {
@@ -442,18 +442,19 @@ export class AbstractType {
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
* @param {Set<string>?} [opts.renderAttrs] - if true, retain rendered+attributed deletes only
* @param {Set<string>?} [opts.renderAttrs] - set of attrs to render. if null, render all attributes
* @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only
* @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
* @return {EventDelta} The Delta representation of this type.
*
* @public
*/
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true } = {}) {
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null } = {}) {
/**
* @type {EventDelta}
*/
const d = /** @type {any} */ (delta.create())
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am)
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deletedItems, itemsToRender)
if (renderChildren) {
/**
* @type {delta.FormattingAttributes}
@@ -625,7 +626,7 @@ export class AbstractType {
* @type {import('../utils/AttributionManager.js').Attribution}
*/
const formattingAttribution = object.assign({}, d.usedAttribution)
const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {}))
const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {}))
if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
// an unattributed formatting attribute was found or an attributed formatting
// attribute was found that resets to the previous status
@@ -635,13 +636,13 @@ export class AbstractType {
const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
if (attributedAt) formattingAttribution.attributedAt = attributedAt
if (attributedAt) formattingAttribution.formatAt = attributedAt
}
if (object.isEmpty(changedAttributedAttributes)) {
d.useAttribution(null)
} else if (attribution != null) {
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
if (attributedAt != null) formattingAttribution.attributedAt = attributedAt
if (attributedAt != null) formattingAttribution.formatAt = attributedAt
d.useAttribution(formattingAttribution)
}
}
@@ -651,7 +652,7 @@ export class AbstractType {
}
}
}
return d
return /** @type {any} */ (d.done())
}
/**
@@ -678,7 +679,7 @@ export class AbstractType {
op.value = op.value.getContentDeep(am)
}
})
return /** @type {any} */ (d)
return /** @type {any} */ (d.done())
}
}
@@ -768,47 +769,6 @@ export const typeListToArray = type => {
return cs
}
/**
* @todo this can be removed as this can be replaced by a generic function
* Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions.
*
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @template {delta.Delta<any,any,any,any>} TypeDelta
* @param {TypeDelta} d
* @param {import('../utils/types.js').YType} type
* @param {import('../internals.js').AbstractAttributionManager} am
*
* @private
* @function
*/
export const typeListGetContent = (d, type, am) => {
type.doc ?? warnPrematureAccess()
/**
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
const cs = []
for (let item = type._start; item !== null; cs.length = 0) {
// populate cs
for (; item !== null && cs.length < 50; item = item.right) {
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
}
for (let i = 0; i < cs.length; i++) {
const c = cs[i]
const attribution = createAttributionFromAttributionItems(c.attrs, c.deleted)
if (c.content.isCountable()) {
if (c.render || attribution != null) {
d.insert(c.content.getContent(), null, attribution)
} else if (!c.deleted) {
d.retain(c.content.getLength())
}
}
}
}
}
/**
* @param {AbstractType<any>} type
* @param {Snapshot} snapshot
@@ -1270,13 +1230,13 @@ export const typeMapGetAll = (parent) => {
* @param {YType_} parent
* @param {Set<string>?} attrsToRender
* @param {import('../internals.js').AbstractAttributionManager} am
* @param {import('../utils/IdSet.js').IdSet?} [deletedItems]
* @param {import('../utils/IdSet.js').IdSet?} [itemsToRender]
*
* @private
* @function
*/
export const typeMapGetDelta = (d, parent, attrsToRender, am) => {
parent.doc ?? warnPrematureAccess()
export const typeMapGetDelta = (d, parent, attrsToRender, am, deletedItems, itemsToRender) => {
/**
* @param {Item} item
* @param {string} key
@@ -1291,28 +1251,17 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am) => {
const c = array.last(content.getContent())
const attribution = createAttributionFromAttributionItems(attrs, deleted)
if (deleted) {
d.unset(key, attribution, c)
} else {
/**
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
let cs = []
for (let prevItem = item.left; prevItem != null; prevItem = prevItem.left) {
/**
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
const tmpcs = []
am.readContent(tmpcs, prevItem.id.client, prevItem.id.clock, prevItem.deleted, prevItem.content, 1)
cs = tmpcs.concat(cs)
if (cs.length === 0 || cs[0].attrs == null) {
cs.splice(0, cs.findIndex(c => c.attrs != null))
break
}
if (cs.length > 0) {
cs.length = 1
}
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
d.unset(key, attribution, c)
}
const prevValue = cs.length > 0 ? array.last(cs[0].content.getContent()) : undefined
} else {
// find prev content
let prevContentItem = item
// this algorithm is problematic. should check all previous content using am.readcontent
for (; prevContentItem.left !== null && deletedItems?.hasId(prevContentItem.left.lastId); prevContentItem = prevContentItem.left) {
// nop
}
const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined
d.set(key, c, attribution, prevValue)
}
}

View File

@@ -17,7 +17,6 @@ import {
callTypeObservers,
transact,
warnPrematureAccess,
typeListGetContent,
typeListSlice,
noAttributionsManager,
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
@@ -108,7 +107,7 @@ export class YArray extends AbstractType {
*/
_callObserver (transaction, parentSubs) {
super._callObserver(transaction, parentSubs)
callTypeObservers(this, transaction, new YEvent(this, transaction, null))
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
}
/**
@@ -214,24 +213,6 @@ export class YArray extends AbstractType {
return super.getContentDeep(am)
}
/**
* Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions.
*
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {delta.ArrayDelta<T>} The Delta representation of this type.
*
* @public
*/
getContent (am = noAttributionsManager) {
const d = this.change
typeListGetContent(d, this, am)
return d
}
/**
* Returns a portion of this YArray into a JavaScript Array selected
* from start to end (end not included).

View File

@@ -758,9 +758,11 @@ export class YText extends AbstractType {
transact(this.doc, transaction => {
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
for (const op of d.children) {
if (delta.$insertOp.check(op)) {
if (op.insert.length > 0 || typeof op.insert !== 'string') {
insertText(transaction, this, currPos, op.insert, op.format || {})
if (delta.$textOp.check(op)) {
insertText(transaction, this, currPos, op.insert, op.format || {})
} else if (delta.$insertOp.check(op)) {
for (let i = 0; i < op.insert.length; i++) {
insertText(transaction, this, currPos, op.insert[i], op.format || {})
}
} else if (delta.$retainOp.check(op)) {
currPos.formatText(transaction, this, op.retain, op.format || {})

View File

@@ -38,8 +38,8 @@ export const attributionJsonSchema = s.$object({
insertedAt: s.$number.optional,
delete: s.$array(s.$string).optional,
deletedAt: s.$number.optional,
attributes: s.$record(s.$string, s.$array(s.$string)).optional,
attributedAt: s.$number.optional
format: s.$record(s.$string, s.$array(s.$string)).optional,
formatAt: s.$number.optional
})
/**

View File

@@ -41,10 +41,6 @@ export class YEvent {
* @type {Transaction}
*/
this.transaction = transaction
/**
* @type {Object|null}
*/
this._changes = null
/**
* @type {null | Map<string, { action: 'add' | 'update' | 'delete', oldValue: any }>}
*/
@@ -183,7 +179,7 @@ export class YEvent {
*/
getDelta (am = noAttributionsManager) {
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: this.childListChanged }))
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: this.childListChanged, deletedItems: this.transaction.deleteSet }))
}
/**

View File

@@ -1,15 +1,4 @@
/**
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt
* |import('../index.js').Array<any>
* |import('../index.js').Map<any>
* |import('../index.js').Text<any>
* |import('../index.js').XmlElement<any>
* |import('../index.js').XmlFragment<any>
* |import('../index.js').XmlText
* |import('../index.js').XmlHook} YValue
*/
/**
* @typedef {import('../types/YArray.js').YArray<any>
* | import('../types/YMap.js').YMap<any>
@@ -17,15 +6,17 @@
* | import('../types/YXmlFragment.js').YXmlFragment<any,any>
* | import('../types/YXmlElement.js').YXmlElement<any,any>
* | import('../types/YXmlHook.js').YXmlHook
* | import('../types/YXmlText.js').YXmlText} YType
* | import('../types/YXmlText.js').YXmlText} YValueType
*/
/**
* @typedef {typeof import('../types/YArray.js').YArray<any>
* | typeof import('../types/YMap.js').YMap<any>
* | typeof import('../types/YText.js').YText<any>
* | typeof import('../types/YXmlFragment.js').YXmlFragment<any,any>
* | typeof import('../types/YXmlElement.js').YXmlElement<any,any>
* | typeof import('../types/YXmlHook.js').YXmlHook
* | typeof import('../types/YXmlText.js').YXmlText} YTypeConstructors
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YValueType} YValue
*/
/**
* @typedef {import('../types/AbstractType.js').AbstractType<any,any>} YType
*/
/**
* @typedef {typeof import('../types/AbstractType.js').AbstractType<any,any>} YTypeConstructors
*/

View File

@@ -35,6 +35,7 @@
"lib0/conditions": "./node_modules/lib0/conditions.js",
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
"lib0/delta": "./node_modules/lib0/delta/d2.js",
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
@@ -201,6 +202,7 @@
"lib0/conditions": "./node_modules/lib0/conditions.js",
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
"lib0/delta": "./node_modules/lib0/delta/d2.js",
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",

View File

@@ -29,7 +29,7 @@ export const testFindTypeInOtherDoc = _tc => {
const ydocClone = new Y.Doc()
Y.applyUpdate(ydocClone, Y.encodeStateAsUpdate(ydoc))
/**
* @template {Y.AbstractType<any>} Type
* @template {import('../src/utils/types.js').YType} Type
* @param {Type} ytype
* @param {Y.Doc} otherYdoc
* @return {Type}
@@ -47,7 +47,7 @@ export const testFindTypeInOtherDoc = _tc => {
if (rootKey == null) {
throw new Error('type does not exist in other ydoc')
}
return /** @type {Type} */ (otherYdoc.get(rootKey, /** @type {typeof Y.AbstractType<any>} */ (ytype.constructor)))
return /** @type {Type} */ (otherYdoc.get(rootKey, /** @type {import('../src/utils/types.js').YTypeConstructors} */ (ytype.constructor)))
} else {
/**
* If it is a sub type, we use the item id to find the history type.

View File

@@ -7,6 +7,8 @@ import * as object from 'lib0/object'
import * as map from 'lib0/map'
import * as Y from '../src/index.js'
import * as math from 'lib0/math'
import * as list from 'lib0/list'
import * as delta from 'lib0/delta'
import {
createIdSet, createIdMap, addToIdSet, encodeIdMap
} from '../src/internals.js'
@@ -484,7 +486,7 @@ export const compare = users => {
t.compare(userArrayValues[i], userArrayValues[i + 1])
t.compare(userMapValues[i], userMapValues[i + 1])
t.compare(userXmlValues[i], userXmlValues[i + 1])
t.compare(userTextValues[i].ops.map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
t.compare(list.toArray(userTextValues[i].children).map(a => delta.$textOp.check(a) ? a.insert : ' ').join('').length, users[i].getText('text').length)
t.compare(userTextValues[i], userTextValues[i + 1], '', (_constructor, a, b) => {
if (a instanceof Y.AbstractType) {
t.compare(a.toJSON(), b.toJSON())

View File

@@ -1,7 +1,7 @@
import * as Y from '../src/index.js'
import { init } from './testHelper.js' // eslint-disable-line
import * as t from 'lib0/testing'
import * as delta from '../src/utils/Delta.js'
import * as delta from 'lib0/delta'
export const testInconsistentFormat = () => {
/**
@@ -11,7 +11,7 @@ export const testInconsistentFormat = () => {
const content = /** @type {Y.XmlText} */ (ydoc.get('text', Y.XmlText))
content.format(0, 6, { bold: null })
content.format(6, 4, { type: 'text' })
t.compare(content.getContent(), delta.createTextDelta().insert('Merge Test', { type: 'text' }).insert(' After', { type: 'text', italic: true }))
t.compare(content.getContent(), delta.create().insert('Merge Test', { type: 'text' }).insert(' After', { type: 'text', italic: true }))
}
const initializeYDoc = () => {
const yDoc = new Y.Doc({ gc: false })
@@ -85,11 +85,11 @@ export const testUndoText = tc => {
t.assert(text0.toString() === 'bcxyz')
// test marks
text0.format(1, 3, { bold: true })
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
t.compare(text0.getContent(), delta.create().insert('b').insert('cxy', { bold: true }).insert('z'))
undoManager.undo()
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'bcxyz' }]))
t.compare(text0.getContent(), delta.create().insert('bcxyz'))
undoManager.redo()
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
t.compare(text0.getContent(), delta.create().insert('b').insert('cxy', { bold: true }).insert('z'))
}
/**
@@ -677,14 +677,10 @@ export const testUndoDeleteTextFormat = _tc => {
undoManager.undo()
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc))
const expect = delta.fromJSON([
{ insert: 'Attack ships ' },
{
insert: 'on fire',
attributes: { bold: true }
},
{ insert: ' off the shoulder of Orion.' }
])
const expect = delta.create()
.insert('Attack ships ')
.insert('on fire', { bold: true })
.insert(' off the shoulder of Orion.')
t.compare(text.getContent(), expect)
t.compare(text2.getContent(), expect)
}

View File

@@ -5,6 +5,7 @@ import { readStructSet, readIdSet, UpdateDecoderV2, UpdateEncoderV2, writeIdSet
import * as encoding from 'lib0/encoding'
import * as decoding from 'lib0/decoding'
import * as object from 'lib0/object'
import * as delta from 'lib0/delta'
/**
* @typedef {Object} Enc
@@ -126,7 +127,7 @@ export const testKeyEncoding = tc => {
const update = Y.encodeStateAsUpdateV2(users[0])
Y.applyUpdateV2(users[1], update)
t.compare(text1.getContent().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }])
t.compare(text1.getContent().toJSON().children, [{ insert: 'c', format: { italic: true } }, { insert: 'b' }, { insert: 'a', format: { italic: true } }])
compare(users)
}
@@ -207,7 +208,7 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => {
}
const meta = enc.parseUpdateMeta(mergedUpdates)
meta.from.forEach((clock, client) => t.assert(clock === 0))
meta.from.forEach((clock, _client) => t.assert(clock === 0))
meta.to.forEach((clock, client) => {
const structs = /** @type {Array<Y.Item>} */ (merged.store.clients.get(client))
const lastStruct = structs[structs.length - 1]
@@ -237,10 +238,10 @@ export const testMergeUpdates1 = _tc => {
}
/**
* @param {t.TestCase} tc
* @param {t.TestCase} _tc
*/
export const testMergeUpdates2 = tc => {
encoders.forEach((enc, i) => {
export const testMergeUpdates2 = _tc => {
encoders.forEach((enc, _i) => {
t.info(`Using encoder: ${enc.description}`)
const ydoc = new Y.Doc({ gc: false })
const updates = /** @type {Array<Uint8Array>} */ ([])
@@ -257,23 +258,23 @@ export const testMergeUpdates2 = tc => {
}
/**
* @param {t.TestCase} tc
* @param {t.TestCase} _tc
*/
export const testMergePendingUpdates = tc => {
export const testMergePendingUpdates = _tc => {
const yDoc = new Y.Doc()
/**
* @type {Array<Uint8Array>}
*/
const serverUpdates = []
yDoc.on('update', (update, origin, c) => {
yDoc.on('update', (update, _origin, _c) => {
serverUpdates.splice(serverUpdates.length, 0, update)
})
const yText = yDoc.getText('textBlock')
yText.applyDelta([{ insert: 'r' }])
yText.applyDelta([{ insert: 'o' }])
yText.applyDelta([{ insert: 'n' }])
yText.applyDelta([{ insert: 'e' }])
yText.applyDelta([{ insert: 'n' }])
yText.applyDelta(delta.create().insert('r'))
yText.applyDelta(delta.create().insert('o'))
yText.applyDelta(delta.create().insert('n'))
yText.applyDelta(delta.create().insert('e'))
yText.applyDelta(delta.create().insert('n'))
const yDoc1 = new Y.Doc()
Y.applyUpdate(yDoc1, serverUpdates[0])
@@ -297,8 +298,7 @@ export const testMergePendingUpdates = tc => {
const yDoc5 = new Y.Doc()
Y.applyUpdate(yDoc5, update4)
Y.applyUpdate(yDoc5, serverUpdates[4])
// @ts-ignore
const _update5 = Y.encodeStateAsUpdate(yDoc5) // eslint-disable-line
Y.encodeStateAsUpdate(yDoc5)
const yText5 = yDoc5.getText('textBlock')
t.compareStrings(yText5.toString(), 'nenor')
@@ -313,7 +313,7 @@ export const testObfuscateUpdates = _tc => {
const ymap = ydoc.getMap('map')
const yarray = ydoc.getArray('array')
// test ytext
ytext.applyDelta([{ insert: 'text', attributes: { bold: true } }, { insert: { href: 'supersecreturl' } }])
ytext.applyDelta(delta.create().insert('text', { bold: true }).insert([{ href: 'supersecreturl' }]))
// test ymap
ymap.set('key', 'secret1')
ymap.set('key', 'secret2')
@@ -330,13 +330,14 @@ export const testObfuscateUpdates = _tc => {
const omap = odoc.getMap('map')
const oarray = odoc.getArray('array')
// test ytext
const delta = /** @type {Array<any>} */ (otext.getContent().toJSON())
t.assert(delta.length === 2)
t.assert(delta[0].insert !== 'text' && delta[0].insert.length === 4)
t.assert(object.length(delta[0].attributes) === 1)
t.assert(!object.hasProperty(delta[0].attributes, 'bold'))
t.assert(object.length(delta[1]) === 1)
t.assert(object.hasProperty(delta[1], 'insert'))
const d = /** @type {any} */ (otext.getContent().toJSON().children)
t.assert(d.length === 2)
const q = d[0]
t.assert(d[0].insert !== 'text' && d[0].insert.length === 4)
t.assert(object.length(d[0].format) === 1)
t.assert(!object.hasProperty(d[0].format, 'bold'))
t.assert(object.length(d[1]) === 1)
t.assert(object.hasProperty(d[1], 'insert'))
// test ymap
t.assert(omap.size === 1)
t.assert(!omap.has('key'))

View File

@@ -4,7 +4,7 @@ import * as t from 'lib0/testing'
import * as prng from 'lib0/prng'
import * as math from 'lib0/math'
import * as env from 'lib0/environment'
import * as delta from '../src/utils/Delta.js'
import * as delta from 'lib0/delta'
const isDevMode = env.getVariable('node_env') === 'development'
@@ -384,24 +384,22 @@ export const testObservedeepIndexes = _tc => {
export const testChangeEvent = tc => {
const { array0, users } = init(tc, { users: 2 })
/**
* @type {any}
* @type {delta.Delta<any,any,any,any,any>}
*/
let changes = null
let d = delta.create()
array0.observe(e => {
changes = e.changes
d = e.delta
})
const newArr = new Y.Array()
array0.insert(0, [newArr, 4, 'dtrn'])
t.assert(changes !== null && changes.added.size === 2 && changes.deleted.size === 0)
t.compare(changes.delta, [{ insert: [newArr, 4, 'dtrn'] }])
changes = null
t.assert(d !== null && d.children.len === 1)
t.compare(d.toJSON().children, [{ insert: [newArr, 4, 'dtrn'] }])
array0.delete(0, 2)
t.assert(changes !== null && changes.added.size === 0 && changes.deleted.size === 2)
t.compare(changes.delta, [{ delete: 2 }])
changes = null
t.assert(d !== null && d.children.len === 1)
t.compare(d.toJSON().children, [{ delete: 2 }])
array0.insert(1, [0.1])
t.assert(changes !== null && changes.added.size === 1 && changes.deleted.size === 0)
t.compare(changes.delta, [{ retain: 1 }, { insert: [0.1] }])
t.assert(d !== null && d.children.len === 2)
t.compare(d.toJSON().children, [{ retain: 1 }, { insert: [0.1] }])
compare(users)
}
@@ -531,7 +529,7 @@ export const testAttributedContent = _tc => {
yarray.delete(0, 1)
yarray.insert(1, [42])
})
const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
const expectedContent = delta.create().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
const attributedContent = yarray.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.assert(attributedContent.equals(expectedContent))

View File

@@ -4,11 +4,12 @@ import {
compareIDs,
noAttributionsManager,
TwosetAttributionManager,
createIdMapFromIdSet,
mapDeltaJsonSchema
createIdMapFromIdSet
} from '../src/internals.js'
import * as t from 'lib0/testing'
import * as prng from 'lib0/prng'
import * as delta from 'lib0/delta'
import * as s from 'lib0/schema'
/**
* @param {t.TestCase} _tc
@@ -490,45 +491,41 @@ export const testThrowsDeleteEventsOnClear = tc => {
export const testChangeEvent = tc => {
const { map0, users } = init(tc, { users: 2 })
/**
* @type {any}
* @type {delta.Delta<any,any,any,any>?}
*/
let changes = null
/**
* @type {any}
*/
let keyChange = null
let changes = delta.create()
map0.observe(e => {
changes = e.changes
changes = e.delta
})
map0.set('a', 1)
keyChange = changes.keys.get('a')
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
let keyChange = changes.attrs.get('a')
t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined)
map0.set('a', 2)
keyChange = changes.keys.get('a')
t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 1)
keyChange = changes.attrs.get('a')
t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === 1)
users[0].transact(() => {
map0.set('a', 3)
map0.set('a', 4)
})
keyChange = changes.keys.get('a')
t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 2)
keyChange = changes.attrs.get('a')
t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === 2)
users[0].transact(() => {
map0.set('b', 1)
map0.set('b', 2)
})
keyChange = changes.keys.get('b')
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
keyChange = changes.attrs.get('b')
t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined)
users[0].transact(() => {
map0.set('c', 1)
map0.delete('c')
})
t.assert(changes !== null && changes.keys.size === 0)
t.assert(changes !== null && changes.attrs.size === 0)
users[0].transact(() => {
map0.set('d', 1)
map0.set('d', 2)
})
keyChange = changes.keys.get('d')
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
keyChange = changes.attrs.get('d')
t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined)
compare(users)
}
@@ -631,24 +628,24 @@ export const testAttributedContent = _tc => {
})
t.group('initial value', () => {
ymap.set('test', 42)
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: undefined, value: 42, attribution: { insert: [] } } })
const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'insert', value: 42, attribution: { insert: [] } }) }
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.compare(expectedContent, attributedContent.toJSON())
t.compare(expectedContent, attributedContent.toJSON().attrs)
})
t.group('overwrite value', () => {
ymap.set('test', 'fourtytwo')
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } })
const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'insert', prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } }) }
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent)
t.compare(expectedContent, attributedContent.toJSON())
t.compare(expectedContent, attributedContent.toJSON().attrs)
})
t.group('delete value', () => {
ymap.delete('test')
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'delete', prevValue: 'fourtytwo', attribution: { delete: [] } } })
const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'delete', prevValue: 'fourtytwo', attribution: { delete: [] } }) }
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent)
t.compare(expectedContent, attributedContent.toJSON())
console.log(attributedContent.toJSON())
t.compare(expectedContent, attributedContent.toJSON().attrs)
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,5 +20,5 @@
}
},
"include": ["./src/**/*.js", "./tests/**/*.js"],
"exclude": ["../lib0/**"]
"exclude": ["./node_modules/**/*"]
}