Implement & test getContentDeep for all types. Improve ability to compare things using lib0/traits.

This commit is contained in:
Kevin Jahns
2025-04-29 18:02:15 +02:00
parent 4f840247a3
commit 1722c8a36f
18 changed files with 311 additions and 118 deletions

8
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "13.6.27", "version": "13.6.27",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lib0": "^0.2.104", "lib0": "^0.2.105",
"y-protocols": "^1.0.5" "y-protocols": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {
@@ -2774,9 +2774,9 @@
} }
}, },
"node_modules/lib0": { "node_modules/lib0": {
"version": "0.2.104", "version": "0.2.105",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.104.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.105.tgz",
"integrity": "sha512-1tqKRANSPTcjs/yjPoKh52oRM2u5AYdd8jie8sDiN8/5kpWWiQSHUGgtB4VEXLw1chVL3QPSPp8q9RWqzSn2FA==", "integrity": "sha512-5vtbuBi2P43ZYOfVMV+TZYkWEa0J9kijXirzEgrPA+nJDQCtMx805/rqW4G1nXbM9IRIhwW+OyNNgcQdbhKfSw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"isomorphic.js": "^0.2.4" "isomorphic.js": "^0.2.4"

View File

@@ -86,7 +86,7 @@
}, },
"homepage": "https://docs.yjs.dev", "homepage": "https://docs.yjs.dev",
"dependencies": { "dependencies": {
"lib0": "^0.2.104", "lib0": "^0.2.105",
"y-protocols": "^1.0.5" "y-protocols": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -10,8 +10,7 @@ import {
ContentAny, ContentAny,
ContentBinary, ContentBinary,
getItemCleanStart, getItemCleanStart,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttrs, AbstractAttributionManager, // eslint-disable-line
createAttributionFromAttrs, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as delta from '../utils/Delta.js' import * as delta from '../utils/Delta.js'
@@ -22,6 +21,13 @@ import * as error from 'lib0/error'
import * as math from 'lib0/math' import * as math from 'lib0/math'
import * as log from 'lib0/logging' import * as log from 'lib0/logging'
/**
* @typedef {delta.ArrayDelta|delta.TextDelta|{ children: delta.ArrayDelta<Array<YXmlDeepContent>> }|{ children: delta.ArrayDelta, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution } } }} YXmlDeepContent
*/
/**
* @typedef {delta.ArrayDelta|delta.TextDelta|{ children: delta.ArrayDelta<Array<DeepContent>> }|{ children: delta.ArrayDelta, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution} } }} DeepContent
*/
/** /**
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats * https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
*/ */
@@ -406,6 +412,22 @@ export class AbstractType {
* @return {any} * @return {any}
*/ */
toJSON () {} toJSON () {}
/**
* @param {AbstractAttributionManager} _am
* @return {any}
*/
getContent (_am) {
error.methodUnimplemented()
}
/**
* @param {AbstractAttributionManager} _am
* @return {DeepContent}
*/
getContentDeep (_am) {
error.methodUnimplemented()
}
} }
/** /**
@@ -476,17 +498,15 @@ export const typeListToArray = type => {
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`. * attribution `{ isDeleted: true, .. }`.
* *
* @template MapType
* @param {AbstractType<any>} type * @param {AbstractType<any>} type
* @param {import('../internals.js').AbstractAttributionManager} am * @param {import('../internals.js').AbstractAttributionManager} am
* @return {delta.Delta<Array<MapType>>} The Delta representation of this type.
* *
* @private * @private
* @function * @function
*/ */
export const typeListGetContent = (type, am) => { export const typeListGetContent = (type, am) => {
type.doc ?? warnPrematureAccess() type.doc ?? warnPrematureAccess()
const d = /** @type {delta.DeltaBuilder<Array<MapType>>} */ (delta.create()) const d = delta.createArrayDelta()
/** /**
* @type {Array<import('../internals.js').AttributedContent<any>>} * @type {Array<import('../internals.js').AttributedContent<any>>}
*/ */
@@ -502,7 +522,7 @@ export const typeListGetContent = (type, am) => {
d.insert(content.getContent(), null, attribution) d.insert(content.getContent(), null, attribution)
} }
} }
return d.done() return d
} }
/** /**
@@ -1016,7 +1036,6 @@ export const typeMapGetContent = (parent, am) => {
return mapcontent return mapcontent
} }
/** /**
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {string} key * @param {string} key

View File

@@ -17,10 +17,13 @@ import {
callTypeObservers, callTypeObservers,
transact, transact,
warnPrematureAccess, warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line typeListGetContent,
AbstractAttributionManager typeListSlice,
noAttributionsManager,
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import { typeListGetContent, typeListSlice } from './AbstractType.js'
import * as delta from '../utils/Delta.js'
/** /**
* Event that describes the changes on a YArray * Event that describes the changes on a YArray
@@ -216,11 +219,31 @@ export class YArray extends AbstractType {
* attribution `{ isDeleted: true, .. }`. * attribution `{ isDeleted: true, .. }`.
* *
* @param {AbstractAttributionManager} am * @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').Delta<Array<T>>} The Delta representation of this type. * @return {import('../utils/Delta.js').ArrayDelta<Array<import('../types/AbstractType.js').DeepContent>>} The Delta representation of this type.
* *
* @public * @public
*/ */
getContent (am) { getContentDeep (am = noAttributionsManager) {
return this.getContent(am).map(d => /** @type {any} */ (
d instanceof delta.InsertOp && d.insert instanceof Array
? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution)
: d
))
}
/**
* 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 {import('../utils/Delta.js').ArrayDelta<Array<T>>} The Delta representation of this type.
*
* @public
*/
getContent (am = noAttributionsManager) {
return typeListGetContent(this, am) return typeListGetContent(this, am)
} }

View File

@@ -13,13 +13,11 @@ import {
YMapRefID, YMapRefID,
callTypeObservers, callTypeObservers,
transact, transact,
typeMapGetContent,
warnPrematureAccess, warnPrematureAccess,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
createAttributionFromAttrs,
typeMapGetContent
} from '../internals.js' } from '../internals.js'
import * as array from 'lib0/array'
import * as iterator from 'lib0/iterator' import * as iterator from 'lib0/iterator'
/** /**

View File

@@ -1000,7 +1000,27 @@ export class YText extends AbstractType {
* attribution `{ isDeleted: true, .. }`. * attribution `{ isDeleted: true, .. }`.
* *
* @param {AbstractAttributionManager} am * @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').Delta<import('../utils/Delta.js').TextDeltaContent>} The Delta representation of this type. * @return {import('../utils/Delta.js').TextDelta<string | import('./AbstractType.js').DeepContent >} The Delta representation of this type.
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
return this.getContent(am).map(d =>
d instanceof delta.InsertOp && d.insert instanceof AbstractType
? new delta.InsertOp(d.insert.getContent(am), d.attributes, d.attribution)
: d
)
}
/**
* 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 {import('../utils/Delta.js').TextDelta} The Delta representation of this type.
* *
* @public * @public
*/ */
@@ -1068,7 +1088,7 @@ export class YText extends AbstractType {
} }
} }
} }
return d.done() return d
} }
/** /**

View File

@@ -13,9 +13,11 @@ import {
YXmlElementRefID, YXmlElementRefID,
typeMapGetContent, typeMapGetContent,
noAttributionsManager, noAttributionsManager,
Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line AbstractAttributionManager, Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as delta from '../utils/Delta.js'
/** /**
* @typedef {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} ValueTypes * @typedef {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} ValueTypes
*/ */
@@ -208,6 +210,36 @@ export class YXmlElement extends YXmlFragment {
return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this)) return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this))
} }
/**
* 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 {{ nodeName: string, children: delta.ArrayDelta<Array<import('./AbstractType.js').DeepContent>>, attributes: import('./AbstractType.js').MapAttributedContent<any> }}
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
const { children: origChildren, attributes: origAttributes } = this.getContent(am)
const children = origChildren.map(d => /** @type {any} */ (
(d instanceof delta.InsertOp && d.insert instanceof Array)
? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
: d
))
/**
* @todo there is a Attributes type and a DeepAttributes type.
* @type {import('./AbstractType.js').MapAttributedContent<any>}
*/
const attributes = {}
object.forEach(origAttributes, (v, key) => {
attributes[key] = Object.assign({}, v, { value: v.value instanceof AbstractType ? v.value.getContentDeep(am) : v.value })
})
return { nodeName: this.nodeName, children, attributes }
}
/** /**
* Render the difference to another ydoc (which can be empty) and highlight the differences with * Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions. * attributions.

View File

@@ -383,13 +383,28 @@ export class YXmlFragment extends AbstractType {
* Calculate the attributed content using the attribution manager. * Calculate the attributed content using the attribution manager.
* *
* @param {import('../internals.js').AbstractAttributionManager} am * @param {import('../internals.js').AbstractAttributionManager} am
* @return {{ children: import('../utils/Delta.js').Delta<Array<YXmlElement|YXmlText|YXmlHook>> }} * @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<YXmlElement|YXmlText|YXmlHook>> }}
*/ */
getContent (am = noAttributionsManager) { getContent (am = noAttributionsManager) {
const children = typeListGetContent(this, am) const children = typeListGetContent(this, am)
return { children } return { children }
} }
/**
* Calculate the attributed content using the attribution manager.
*
* @param {import('../internals.js').AbstractAttributionManager} am
* @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>> }}
*/
getContentDeep (am) {
const { children: origChildren } = this.getContent()
/**
* @type {import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>>}
*/
const children = origChildren.map(d => /** @type {any} */ (d instanceof AbstractType ? d.getContentDeep(am) : d))
return { children }
}
/** /**
* Appends content to this YArray. * Appends content to this YArray.
* *

View File

@@ -1,5 +1,6 @@
import * as object from 'lib0/object' import * as object from 'lib0/object'
import * as fun from 'lib0/function' import * as fun from 'lib0/function'
import * as traits from 'lib0/traits'
/** /**
* @template {string|Array<any>|{[key: string]: any}} Content * @template {string|Array<any>|{[key: string]: any}} Content
@@ -29,9 +30,20 @@ export class InsertOp {
this.attribution = attribution this.attribution = attribution
} }
get length () {
return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1
}
toJSON () { toJSON () {
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({})) return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({}))
} }
/**
* @param {InsertOp<Content>} other
*/
[traits.EqualityTraitSymbol] (other) {
return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
}
} }
export class DeleteOp { export class DeleteOp {
@@ -42,9 +54,20 @@ export class DeleteOp {
this.delete = len this.delete = len
} }
get length () {
return 0
}
toJSON () { toJSON () {
return { delete: this.delete } return { delete: this.delete }
} }
/**
* @param {DeleteOp} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.delete === other.delete
}
} }
export class RetainOp { export class RetainOp {
@@ -59,16 +82,48 @@ export class RetainOp {
this.attribution = attribution this.attribution = attribution
} }
get length () {
return this.retain
}
toJSON () { toJSON () {
return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {}) return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {})
} }
/**
* @param {RetainOp} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.retain === other.retain && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
}
} }
/** /**
* @template {string|Array<any>|{[key: string]: any}} Content * @typedef {Array<any>} ArrayDeltaContent
*/ */
export class Delta {
constructor () { /**
* @typedef {string | { [key: string]: any }} TextDeltaContent
*/
/**
* @typedef {{ array: ArrayDeltaContent, text: TextDeltaContent, custom: string|Array<any>|{[key:string]:any}}} DeltaTypeMapper
*/
/**
* @typedef {(TextDelta | ArrayDelta)} Delta
*/
/**
* @template {'array' | 'text' | 'custom'} Type
* @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
*/
export class AbstractDelta {
/**
* @param {Type} type
*/
constructor (type) {
this.type = type
/** /**
* @type {Array<DeltaOp<Content>>} * @type {Array<DeltaOp<Content>>}
*/ */
@@ -76,48 +131,49 @@ export class Delta {
} }
/** /**
* @param {Delta<Content>} d * @template {DeltaTypeMapper[Type]} MContent
* @param {(d:DeltaOp<Content>)=>DeltaOp<MContent>} f
* @return {DeltaBuilder<Type, MContent>}
*/
map (f) {
const d = /** @type {DeltaBuilder<Type,any>} */ (new /** @type {any} */ (this.constructor)(this.type))
d.ops = this.ops.map(f)
// @ts-ignore
d._lastOp = d.ops[d.ops.length - 1] ?? null
return d
}
/**
* @param {(d:DeltaOp<Content>,index:number)=>void} f
*/
forEach (f) {
for (
let i = 0, index = 0, op = this.ops[i];
i < this.ops.length;
i++, index += op.length, op = this.ops[i]
) {
f(op, index)
}
}
/**
* @param {AbstractDelta<Type, Content>} other
* @return {boolean} * @return {boolean}
*/ */
equals (d) { equals (other) {
return this.ops.length === d.ops.length && this.ops.every((op, i) => { return this[traits.EqualityTraitSymbol](other)
const dop = d.ops[i]
if (op.constructor !== dop.constructor) return false
switch (op.constructor) {
case DeleteOp: {
if (/** @type {DeleteOp} */ (op).delete !== /** @type {DeleteOp} */ (dop).delete) {
return false
}
break
}
case InsertOp: {
if (
!fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).insert, /** @type {InsertOp<Content>} */ (dop).insert)
|| !fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).attributes, /** @type {InsertOp<Content>} */ (dop).attributes)
|| !fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).attribution, /** @type {InsertOp<Content>} */ (dop).attribution)
) {
return false
}
break
}
case RetainOp: {
if (
/** @type {RetainOp} */ (op).retain !== /** @type {RetainOp} */ (dop).retain
|| !fun.equalityDeep(/** @type {RetainOp} */ (op).attributes, /** @type {RetainOp} */ (dop).attributes)
|| !fun.equalityDeep(/** @type {RetainOp} */ (op).attribution, /** @type {RetainOp} */ (dop).attribution)
) {
return false
}
break
}
}
return true
})
} }
toJSON () { toJSON () {
return { ops: this.ops.map(o => o.toJSON()) } return { ops: this.ops.map(o => o.toJSON()) }
} }
/**
* @param {AbstractDelta<Type,Content>} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.type === other.type && fun.equalityDeep(this.ops, other.ops)
}
} }
/** /**
@@ -134,12 +190,16 @@ const mergeAttrs = (a, b) => {
} }
/** /**
* @template {string|Array<any>|{[key: string]: any}} Content * @template {'array' | 'text' | 'custom'} [Type='custom']
* @extends Delta<Content> * @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
* @extends AbstractDelta<Type,Content>
*/ */
export class DeltaBuilder extends Delta { export class DeltaBuilder extends AbstractDelta {
constructor () { /**
super() * @param {Type} type
*/
constructor (type) {
super(type)
/** /**
* @type {FormattingAttributes?} * @type {FormattingAttributes?}
*/ */
@@ -185,10 +245,10 @@ export class DeltaBuilder extends Delta {
const mergedAttribution = attribution == null ? this.usedAttribution : mergeAttrs(this.usedAttribution, attribution) const mergedAttribution = attribution == null ? this.usedAttribution : mergeAttrs(this.usedAttribution, attribution)
if (this._lastOp instanceof InsertOp && (mergedAttributes === this._lastOp.attributes || fun.equalityDeep(mergedAttributes, this._lastOp.attributes)) && (mergedAttribution === this._lastOp.attribution || fun.equalityDeep(mergedAttribution, this._lastOp.attribution))) { if (this._lastOp instanceof InsertOp && (mergedAttributes === this._lastOp.attributes || fun.equalityDeep(mergedAttributes, this._lastOp.attributes)) && (mergedAttribution === this._lastOp.attribution || fun.equalityDeep(mergedAttribution, this._lastOp.attribution))) {
if (insert.constructor === String) { if (insert.constructor === String) {
// @ts-ignore // @ts-ignore
this._lastOp.insert += insert this._lastOp.insert += insert
} else if (insert.constructor === Array && this._lastOp.insert.constructor === Array) { } else if (insert.constructor === Array && this._lastOp.insert.constructor === Array) {
// @ts-ignore // @ts-ignore
this._lastOp.insert.push(...insert) this._lastOp.insert.push(...insert)
} else { } else {
this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution)) this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution))
@@ -230,31 +290,39 @@ export class DeltaBuilder extends Delta {
} }
/** /**
* @return {Delta<Content>} * @return {AbstractDelta<Type,Content>}
*/ */
done () { done () {
return this return this
} }
} }
export const create = () => new DeltaBuilder() /**
* @template {ArrayDeltaContent} [Content=ArrayDeltaContent]
* @extends DeltaBuilder<'array',Content>
*/
export class ArrayDelta extends DeltaBuilder {
constructor () {
super('array')
}
}
/** /**
* @typedef {string | { [key: string]: any }} TextDeltaContent * @template {TextDeltaContent} [Content=TextDeltaContent]
* @extends DeltaBuilder<'text',Content>
*/ */
export class TextDelta extends DeltaBuilder {
constructor () {
super('text')
}
}
/** /**
* @template {TextDeltaContent} Content * @return {TextDelta<TextDeltaContent>}
* @return {DeltaBuilder<Content>}
*/ */
export const createTextDelta = () => new DeltaBuilder() export const createTextDelta = () => new TextDelta()
/** /**
* @typedef {Array<any>} ArrayDeltaContent * @return {ArrayDelta<ArrayDeltaContent>}
*/ */
export const createArrayDelta = () => new ArrayDelta()
/**
* @template {ArrayDeltaContent} Content
* @return {DeltaBuilder<Content>}
*/
export const createArrayDelta = () => new DeltaBuilder()

View File

@@ -416,7 +416,6 @@ export const createInsertionSetFromStructStore = ss => {
return idset return idset
} }
/** /**
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
* @param {IdSet} idSet * @param {IdSet} idSet

View File

@@ -135,6 +135,9 @@
"lib0/symbol.js": "./node_modules/lib0/symbol.js", "lib0/symbol.js": "./node_modules/lib0/symbol.js",
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs", "lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
"lib0/symbol": "./node_modules/lib0/symbol.js", "lib0/symbol": "./node_modules/lib0/symbol.js",
"lib0/traits.js": "./node_modules/lib0/traits.js",
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
"lib0/traits": "./node_modules/lib0/traits.js",
"lib0/testing.js": "./node_modules/lib0/testing.js", "lib0/testing.js": "./node_modules/lib0/testing.js",
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs", "lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
"lib0/testing": "./node_modules/lib0/testing.js", "lib0/testing": "./node_modules/lib0/testing.js",
@@ -296,6 +299,9 @@
"lib0/symbol.js": "./node_modules/lib0/symbol.js", "lib0/symbol.js": "./node_modules/lib0/symbol.js",
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs", "lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
"lib0/symbol": "./node_modules/lib0/symbol.js", "lib0/symbol": "./node_modules/lib0/symbol.js",
"lib0/traits.js": "./node_modules/lib0/traits.js",
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
"lib0/traits": "./node_modules/lib0/traits.js",
"lib0/testing.js": "./node_modules/lib0/testing.js", "lib0/testing.js": "./node_modules/lib0/testing.js",
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs", "lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
"lib0/testing": "./node_modules/lib0/testing.js", "lib0/testing": "./node_modules/lib0/testing.js",

View File

@@ -1,6 +1,6 @@
import * as t from 'lib0/testing' import * as t from 'lib0/testing'
import * as idmap from '../src/utils/IdMap.js' import * as idmap from '../src/utils/IdMap.js'
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution, validateIdMap } from './testHelper.js' import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution } from './testHelper.js'
import * as YY from '../src/internals.js' import * as YY from '../src/internals.js'
/** /**

View File

@@ -5,7 +5,7 @@ import * as delta from '../src/utils/Delta.js'
* @param {t.TestCase} _tc * @param {t.TestCase} _tc
*/ */
export const testDelta = _tc => { export const testDelta = _tc => {
const d = delta.create().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done() const d = delta.createTextDelta().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done()
t.compare(d.toJSON().ops, [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }]) t.compare(d.toJSON().ops, [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }])
} }
@@ -13,7 +13,7 @@ export const testDelta = _tc => {
* @param {t.TestCase} _tc * @param {t.TestCase} _tc
*/ */
export const testDeltaMerging = _tc => { export const testDeltaMerging = _tc => {
const d = delta.create() const d = delta.createTextDelta()
.insert('hello') .insert('hello')
.insert('world') .insert('world')
.insert(' ', { italic: true }) .insert(' ', { italic: true })

View File

@@ -19,7 +19,6 @@ export const testAfterTransactionRecursion = _tc => {
}, 'test') }, 'test')
} }
/** /**
* @param {t.TestCase} _tc * @param {t.TestCase} _tc
*/ */
@@ -39,7 +38,7 @@ export const testFindTypeInOtherDoc = _tc => {
const ydoc = /** @type {Y.Doc} */ (ytype.doc) const ydoc = /** @type {Y.Doc} */ (ytype.doc)
if (ytype._item === null) { if (ytype._item === null) {
/** /**
* If is a root type, we need to find the root key in the original ydoc * If is a root type, we need to find the root key in the original ydoc
* and use it to get the type in the other ydoc. * and use it to get the type in the other ydoc.
*/ */
const rootKey = Array.from(ydoc.share.keys()).find( const rootKey = Array.from(ydoc.share.keys()).find(
@@ -68,7 +67,6 @@ export const testFindTypeInOtherDoc = _tc => {
t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null) t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null)
} }
/** /**
* @param {t.TestCase} _tc * @param {t.TestCase} _tc
*/ */

View File

@@ -528,8 +528,8 @@ export const testAttributedContent = _tc => {
yarray.delete(0, 1) yarray.delete(0, 1)
yarray.insert(1, [42]) yarray.insert(1, [42])
}) })
let expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] }) const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
let attributedContent = yarray.getContent(attributionManager) const attributedContent = yarray.getContent(attributionManager)
console.log(attributedContent.toJSON().ops) console.log(attributedContent.toJSON().ops)
t.assert(attributedContent.equals(expectedContent)) t.assert(attributedContent.equals(expectedContent))
}) })

View File

@@ -1,6 +1,5 @@
import * as Y from '../src/index.js' import * as Y from '../src/index.js'
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
import * as delta from '../src/utils/Delta.js'
import { import {
compareIDs, compareIDs,
noAttributionsManager, noAttributionsManager,
@@ -631,22 +630,22 @@ export const testAttributedContent = _tc => {
}) })
t.group('initial value', () => { t.group('initial value', () => {
ymap.set('test', 42) ymap.set('test', 42)
let expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } } const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } }
let attributedContent = ymap.getContent(attributionManager) const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent) console.log(attributedContent)
t.compare(expectedContent, attributedContent) t.compare(expectedContent, attributedContent)
}) })
t.group('overwrite value', () => { t.group('overwrite value', () => {
ymap.set('test', 'fourtytwo') ymap.set('test', 'fourtytwo')
let expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } } const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } }
let attributedContent = ymap.getContent(attributionManager) const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent) console.log(attributedContent)
t.compare(expectedContent, attributedContent) t.compare(expectedContent, attributedContent)
}) })
t.group('delete value', () => { t.group('delete value', () => {
ymap.delete('test') ymap.delete('test')
let expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } } const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } }
let attributedContent = ymap.getContent(attributionManager) const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent) console.log(attributedContent)
t.compare(expectedContent, attributedContent) t.compare(expectedContent, attributedContent)
}) })

View File

@@ -2316,15 +2316,15 @@ export const testAttributedContent = _tc => {
}) })
t.group('insert / delete / format', () => { t.group('insert / delete / format', () => {
ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }]) ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }])
let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!') const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!')
let attributedContent = ytext.getContent(attributionManager) const attributedContent = ytext.getContent(attributionManager)
console.log(attributedContent.toJSON().ops) console.log(attributedContent.toJSON().ops)
t.assert(attributedContent.equals(expectedContent)) t.assert(attributedContent.equals(expectedContent))
}) })
t.group('unformat', () => { t.group('unformat', () => {
ytext.applyDelta([{retain: 5, attributes: { italic: null }}]) ytext.applyDelta([{ retain: 5, attributes: { italic: null } }])
let expectedContent = delta.create().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!') const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
let attributedContent = ytext.getContent(attributionManager) const attributedContent = ytext.getContent(attributionManager)
console.log(attributedContent.toJSON().ops) console.log(attributedContent.toJSON().ops)
t.assert(attributedContent.equals(expectedContent)) t.assert(attributedContent.equals(expectedContent))
}) })
@@ -2355,9 +2355,9 @@ export const testAttributedDiffing = _tc => {
// implementations is the TwosetAttributionManager // implementations is the TwosetAttributionManager
const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions) const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions)
// we render the attributed content with the attributionManager // we render the attributed content with the attributionManager
let attributedContent = ytext.getContent(attributionManager) const attributedContent = ytext.getContent(attributionManager)
console.log(JSON.stringify(attributedContent.toJSON().ops, null, 2)) console.log(JSON.stringify(attributedContent.toJSON().ops, null, 2))
let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!') const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!')
t.assert(attributedContent.equals(expectedContent)) t.assert(attributedContent.equals(expectedContent))
console.log(Y.encodeIdMap(attributedInsertions).length) console.log(Y.encodeIdMap(attributedInsertions).length)
} }
@@ -2588,14 +2588,14 @@ const checkResult = result => {
*/ */
const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d
t.info('length of text = ' + result.users[i-1].getText('text').length) t.info('length of text = ' + result.users[i - 1].getText('text').length)
t.measureTime('original toDelta perf', () => { t.measureTime('original toDelta perf', () => {
result.users[i-1].getText('text').toDelta().map(typeToObject) result.users[i - 1].getText('text').toDelta().map(typeToObject)
}) })
t.measureTime('getContent(attributionManager) performance)', () => { t.measureTime('getContent(attributionManager) performance)', () => {
result.users[i-1].getText('text').getContent() result.users[i - 1].getText('text').getContent()
}) })
const p1 = result.users[i-1].getText('text').toDelta().map(typeToObject) const p1 = result.users[i - 1].getText('text').toDelta().map(typeToObject)
const p2 = result.users[i].getText('text').toDelta().map(typeToObject) const p2 = result.users[i].getText('text').toDelta().map(typeToObject)
t.compare(p1, p2) t.compare(p1, p2)
} }
@@ -2629,7 +2629,7 @@ export const testAttributionManagerDefaultPerformance = tc => {
ytext.insert(index, content) ytext.insert(index, content)
} }
} }
t.info(`number of changes: ${N/1000}k`) t.info(`number of changes: ${N / 1000}k`)
t.info(`length of text: ${ytext.length}`) t.info(`length of text: ${ytext.length}`)
const M = 100 const M = 100
t.measureTime(`original toString perf <executed ${M} times>`, () => { t.measureTime(`original toString perf <executed ${M} times>`, () => {

View File

@@ -243,8 +243,8 @@ export const testFragmentAttributedContent = _tc => {
yfragment.delete(0, 1) yfragment.delete(0, 1)
yfragment.insert(1, [elem3]) yfragment.insert(1, [elem3])
}) })
let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
let attributedContent = yfragment.getContent(attributionManager) const attributedContent = yfragment.getContent(attributionManager)
console.log(attributedContent.children.toJSON().ops) console.log(attributedContent.children.toJSON().ops)
t.assert(attributedContent.children.equals(expectedContent)) t.assert(attributedContent.children.equals(expectedContent))
t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON())
@@ -257,8 +257,8 @@ export const testFragmentAttributedContent = _tc => {
export const testElementAttributedContent = _tc => { export const testElementAttributedContent = _tc => {
const ydoc = new Y.Doc({ gc: false }) const ydoc = new Y.Doc({ gc: false })
const yelement = ydoc.getXmlElement('p') const yelement = ydoc.getXmlElement('p')
const elem1 = new Y.XmlElement('span') const elem1 = new Y.XmlText('hello')
const elem2 = new Y.XmlText('hello') const elem2 = new Y.XmlElement('span')
const elem3 = new Y.XmlText('world') const elem3 = new Y.XmlText('world')
yelement.insert(0, [elem1, elem2]) yelement.insert(0, [elem1, elem2])
let attributionManager = Y.noAttributionsManager let attributionManager = Y.noAttributionsManager
@@ -272,12 +272,28 @@ export const testElementAttributedContent = _tc => {
yelement.insert(1, [elem3]) yelement.insert(1, [elem3])
yelement.setAttribute('key', '42') yelement.setAttribute('key', '42')
}) })
let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
let attributedContent = yelement.getContent(attributionManager) const attributedContent = yelement.getContent(attributionManager)
console.log('children', attributedContent.children.toJSON().ops) console.log('children', attributedContent.children.toJSON().ops)
console.log('attributes', attributedContent.attributes) console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent)) t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } }) t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.group('test getContentDeep', () => {
const expectedContent = delta.createArrayDelta().insert(
[delta.createTextDelta().insert('hello', null, { delete: [] })],
null,
{ delete: [] }
).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }])
.insert([
delta.createTextDelta().insert('world', null, { insert: [] })
], null, { insert: [] })
const attributedContent = yelement.getContentDeep(attributionManager)
console.log('children', JSON.stringify(attributedContent.children.toJSON().ops, null, 2))
console.log('cs expec', JSON.stringify(expectedContent.toJSON().ops, null, 2))
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
}) })
} }