From 72393e6ce8aef65cd96ff18d51de409f01453847 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 7 Jul 2025 20:23:50 +0200 Subject: [PATCH] work on new deltas with modifiers --- src/types/AbstractType.js | 21 +- src/types/YArray.js | 4 +- src/types/YMap.js | 10 +- src/types/YText.js | 18 +- src/types/YXmlElement.js | 16 +- src/types/YXmlFragment.js | 4 +- src/types/YXmlText.js | 4 +- src/utils/Delta.js | 470 +++++++++++++++++++++++++++++++++-- tests/compatibility.tests.js | 2 +- tests/delta.tests.js | 89 +++++++ tests/undo-redo.tests.js | 12 +- tests/updates.tests.js | 4 +- tests/y-array.tests.js | 2 +- tests/y-map.tests.js | 6 +- tests/y-text.tests.js | 64 ++--- tests/y-xml.tests.js | 8 +- 16 files changed, 613 insertions(+), 121 deletions(-) diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index ce453e8a..a5e3db83 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -417,7 +417,7 @@ export class AbstractType { * @param {AbstractAttributionManager} _am * @return {any} */ - getContent (_am) { + getDelta (_am) { error.methodUnimplemented() } @@ -980,11 +980,6 @@ export const typeMapGetAll = (parent) => { return res } -/** - * @template MapType - * @typedef {{ [key: string]: { prevValue: MapType | undefined, value: MapType | undefined, attribution: any } }} MapAttributedContent - */ - /** * Render the difference to another ydoc (which can be empty) and highlight the differences with * attributions. @@ -995,16 +990,12 @@ export const typeMapGetAll = (parent) => { * @template MapType * @param {AbstractType} parent * @param {import('../internals.js').AbstractAttributionManager} am - * @return {MapAttributedContent} The Delta representation of this type. * * @private * @function */ -export const typeMapGetContent = (parent, am) => { - /** - * @type {MapAttributedContent} - */ - const mapcontent = {} +export const typeMapGetDelta = (parent, am) => { + const mapdelta = /** @type {delta.MapDeltaBuilder<{ [key:string]: MapType }>} */ (delta.createMapDelta()) parent.doc ?? warnPrematureAccess() parent._map.forEach((item, key) => { /** @@ -1016,7 +1007,7 @@ export const typeMapGetContent = (parent, am) => { const c = array.last(content.getContent()) const attribution = createAttributionFromAttributionItems(attrs, deleted) if (deleted) { - mapcontent[key] = { prevValue: c, value: undefined, attribution } + mapdelta.delete(key, c, attribution) } else { /** * @type {Array>} @@ -1038,10 +1029,10 @@ export const typeMapGetContent = (parent, am) => { } } const prevValue = cs.length > 0 ? array.last(cs[0].content.getContent()) : undefined - mapcontent[key] = { prevValue, value: c, attribution } + mapdelta.set(key, c, prevValue, attribution) } }) - return mapcontent + return mapdelta } /** diff --git a/src/types/YArray.js b/src/types/YArray.js index 5f996755..5a1cfa42 100644 --- a/src/types/YArray.js +++ b/src/types/YArray.js @@ -224,7 +224,7 @@ export class YArray extends AbstractType { * @public */ getContentDeep (am = noAttributionsManager) { - return this.getContent(am).map(d => /** @type {any} */ ( + return this.getDelta(am).map(d => /** @type {any} */ ( d instanceof delta.InsertArrayOp && d.insert instanceof Array ? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution) : d @@ -243,7 +243,7 @@ export class YArray extends AbstractType { * * @public */ - getContent (am = noAttributionsManager) { + getDelta (am = noAttributionsManager) { return typeListGetContent(this, am) } diff --git a/src/types/YMap.js b/src/types/YMap.js index 7c0e0cbe..fd17252e 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -13,9 +13,9 @@ import { YMapRefID, callTypeObservers, transact, - typeMapGetContent, + typeMapGetDelta, warnPrematureAccess, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line + MapDelta, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' import * as iterator from 'lib0/iterator' @@ -195,12 +195,12 @@ export class YMap extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {import('../internals.js').AbstractAttributionManager} am - * @return {import('./AbstractType.js').MapAttributedContent} The Delta representation of this type. + * @return {MapDelta<{[key:string]: MapType}>} The Delta representation of this type. * * @public */ - getContent (am) { - return typeMapGetContent(this, am) + getDelta (am) { + return typeMapGetDelta(this, am) } /** diff --git a/src/types/YText.js b/src/types/YText.js index 45e34f74..1e26ede2 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -789,7 +789,7 @@ export class YText extends AbstractType { * @type {YText} */ const text = new YText() - text.applyDelta(this.getContent()) + text.applyDelta(this.getDelta()) return text } @@ -887,23 +887,13 @@ export class YText extends AbstractType { * @public */ getContentDeep (am = noAttributionsManager) { - return this.getContent(am).map(d => + return this.getDelta(am).map(d => d instanceof delta.InsertEmbedOp && d.insert instanceof AbstractType - ? new delta.InsertEmbedOp(d.insert.getContent(am), d.attributes, d.attribution) + ? new delta.InsertEmbedOp(d.insert.getDelta(am), d.attributes, d.attribution) : d ) } - /** - * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. - * - * @public - */ - getContent (am = noAttributionsManager) { - return this.getDelta(am) - } - /** * Render the difference to another ydoc (which can be empty) and highlight the differences with * attributions. @@ -1301,7 +1291,7 @@ export class YText extends AbstractType { * @param {this} other */ [traits.EqualityTraitSymbol] (other) { - return this.getContent().equals(other.getContent()) + return this.getDelta().equals(other.getDelta()) } } diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index 8d3527cd..833fbfdf 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -11,7 +11,7 @@ import { typeMapGetAllSnapshot, typeListForEach, YXmlElementRefID, - typeMapGetContent, + typeMapGetDelta, noAttributionsManager, AbstractAttributionManager, Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line } from '../internals.js' @@ -223,7 +223,7 @@ export class YXmlElement extends YXmlFragment { * @public */ getContentDeep (am = noAttributionsManager) { - const { children: origChildren, attributes: origAttributes } = this.getContent(am) + const { children: origChildren, attributes: origAttributes } = this.getDelta(am) const children = origChildren.map(d => /** @type {any} */ ( (d instanceof delta.InsertArrayOp && d.insert instanceof Array) ? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution) @@ -231,9 +231,9 @@ export class YXmlElement extends YXmlFragment { )) /** * @todo there is a Attributes type and a DeepAttributes type. - * @type {import('./AbstractType.js').MapAttributedContent} + * @type {delta.MapDelta<>} */ - const attributes = {} + const attributes = delta.createMapDelta() object.forEach(origAttributes, (v, key) => { attributes[key] = Object.assign({}, v, { value: v.value instanceof AbstractType ? v.value.getContentDeep(am) : v.value }) }) @@ -251,10 +251,10 @@ export class YXmlElement extends YXmlFragment { * * @public */ - getContent (am = noAttributionsManager) { - const attributes = typeMapGetContent(this, am) - const { children } = super.getContent(am) - return { nodeName: this.nodeName, children, attributes } + getDelta (am = noAttributionsManager) { + const { children } = super.getDelta(am) + const attributes = typeMapGetDelta(this, am) + return new delta.XmlDelta(this.nodeName, children, attributes) } /** diff --git a/src/types/YXmlFragment.js b/src/types/YXmlFragment.js index 804d442c..e63e0d8a 100644 --- a/src/types/YXmlFragment.js +++ b/src/types/YXmlFragment.js @@ -386,7 +386,7 @@ export class YXmlFragment extends AbstractType { * @param {import('../internals.js').AbstractAttributionManager} am * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} */ - getContent (am = noAttributionsManager) { + getDelta (am = noAttributionsManager) { const children = typeListGetContent(this, am) return { children } } @@ -398,7 +398,7 @@ export class YXmlFragment extends AbstractType { * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} */ getContentDeep (am) { - const { children: origChildren } = this.getContent(am) + const { children: origChildren } = this.getDelta(am) /** * @type {import('../utils/Delta.js').ArrayDelta>} */ diff --git a/src/types/YXmlText.js b/src/types/YXmlText.js index 7b5ac37c..195a1e3d 100644 --- a/src/types/YXmlText.js +++ b/src/types/YXmlText.js @@ -40,7 +40,7 @@ export class YXmlText extends YText { */ clone () { const text = new YXmlText() - text.applyDelta(this.getContent()) + text.applyDelta(this.getDelta()) return text } @@ -68,7 +68,7 @@ export class YXmlText extends YText { } toString () { - return this.getContent().ops.map(dop => { + return this.getDelta().ops.map(dop => { if (dop instanceof delta.InsertStringOp) { const nestedNodes = [] for (const nodeName in dop.attributes) { diff --git a/src/utils/Delta.js b/src/utils/Delta.js index cf9c5476..f140b693 100644 --- a/src/utils/Delta.js +++ b/src/utils/Delta.js @@ -1,4 +1,5 @@ import * as object from 'lib0/object' +import * as map from 'lib0/map' import * as fun from 'lib0/function' import * as traits from 'lib0/traits' import * as error from 'lib0/error' @@ -6,12 +7,14 @@ import * as error from 'lib0/error' /** * @template {any} ArrayContent * @template {object} Embeds - * @typedef {InsertStringOp|InsertEmbedOp|InsertArrayOp|RetainOp|DeleteOp} DeltaOp + * @template {Delta|undefined} ModifyingDelta + * @typedef {InsertStringOp|InsertEmbedOp|InsertArrayOp|RetainOp|DeleteOp|(ModifyingDelta extends undefined ? never : ModifyOp)} DeltaOp */ /** * @template {object} Embeds - * @typedef {InsertStringOp|InsertEmbedOp|RetainOp|DeleteOp} TextDeltaOp + * @template {Delta|undefined} Modifiers + * @typedef {InsertStringOp|InsertEmbedOp|RetainOp|DeleteOp|(Modifiers extends undefined ? never : ModifyOp)} TextDeltaOp */ /** @@ -32,7 +35,7 @@ import * as error from 'lib0/error' */ /** - * @typedef {{ insert: string|object, attributes?: { [key: string]: any }, attribution?: Attribution_ } | { delete: number } | { retain: number, attributes?: { [key:string]: any }, attribution?: Attribution_ }} DeltaJsonOp + * @typedef {{ insert: string|object, attributes?: { [key: string]: any }, attribution?: Attribution_ } | { delete: number } | { retain: number, attributes?: { [key:string]: any }, attribution?: Attribution_ } | { modify: object }} DeltaJsonOp */ export class InsertStringOp { @@ -193,18 +196,71 @@ export class RetainOp { } /** - * @typedef {(TextDelta | ArrayDelta)} Delta + * Delta that can be applied on a YType Embed + * + * @template {Delta} DTypes + */ +export class ModifyOp { + /** + * @param {DTypes} delta + */ + constructor (delta) { + this.modify = delta + } + + get length () { + return 1 + } + + /** + * @return {DeltaJsonOp} + */ + toJSON () { + return { modify: this.modify.toJSON() } + } + + /** + * @param {ModifyOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.modify[traits.EqualityTraitSymbol](other.modify) + } +} + +export class AbstractDelta { + constructor () { + this.remote = false + /** + * @type {any} origin + */ + this.origin = null + this.isDiff = true + } + + /** + * @param {any} _other + */ + [traits.EqualityTraitSymbol] (_other) { + error.methodUnimplemented() + } +} + +/** + * @template {Delta|undefined} [Modifiers=any] + * @typedef {(TextDelta | ArrayDelta | MapDelta | XmlDelta )} Delta */ /** * @template {'array' | 'text' | 'custom'} Type - * @template {DeltaOp} TDeltaOp + * @template {DeltaOp} TDeltaOp + * @template {Delta|undefined} Modifiers */ -export class AbstractDelta { +export class AbstractArrayDelta extends AbstractDelta { /** * @param {Type} type */ constructor (type) { + super() this.type = type /** * @type {Array} @@ -213,12 +269,12 @@ export class AbstractDelta { } /** - * @template {(d:TDeltaOp) => DeltaOp} Mapper + * @template {(d:TDeltaOp) => DeltaOp} Mapper * @param {Mapper} f - * @return {DeltaBuilder infer OP ? OP : unknown>} + * @return {DeltaBuilder infer OP ? OP : unknown,Modifiers>} */ map (f) { - const d = /** @type {DeltaBuilder} */ (new /** @type {any} */ (this.constructor)(this.type)) + const d = /** @type {DeltaBuilder} */ (new /** @type {any} */ (this.constructor)(this.type)) d.ops = this.ops.map(f) // @ts-ignore d.lastOp = d.ops[d.ops.length - 1] ?? null @@ -226,20 +282,61 @@ export class AbstractDelta { } /** - * @param {(d:TDeltaOp,index:number)=>void} f + * + * Iterate through the changes. There are two approches to iterate through the changes. The + * following examples achieve the same thing: + * + * @example + * d.forEach((op, index) => { + * if (op instanceof delta.InsertArrayOp) { + * op.insert + * } else if (op instanceof delta.RetainOp ) { + * op.retain + * } else if (op instanceof delta.DeleteOp) { + * op.delete + * } + * }) + * + * The second approach doesn't require instanceof checks. + * + * @example + * d.forEach(null, + * (insertOp, index) => insertOp.insert, + * (retainOp, index) => insertOp.retain + * (deleteOp, index) => insertOp.delete + * ) + * + * @param {null|((d:TDeltaOp,index:number)=>void)} f + * @param {null|((insertOp: (InsertEmbedOp | InsertStringOp | InsertArrayOp) & TDeltaOp,index:number)=>void)} insertHandler + * @param {null|((retainOp:RetainOp,index:number)=>void)} retainHandler + * @param {null|((deleteOp:DeleteOp,index:number)=>void)} deleteHandler + * @param {null|(Modifiers extends undefined ? null : ((modifyOp:ModifyOp,index:number)=>void))} modifyHandler */ - forEach (f) { + forEach (f = null, insertHandler = null, retainHandler = null, deleteHandler = null, modifyHandler = null) { 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) + f?.(op, index) + switch (op.constructor) { + case RetainOp: + retainHandler?.(/** @type {RetainOp} */ (op), index) + break + case DeleteOp: + deleteHandler?.(/** @type {DeleteOp} */ (op), index) + break + case ModifyOp: + modifyHandler?.(/** @type {any}) */ (op), index) + break + default: + insertHandler?.(/** @type {any} */ (op), index) + } } } /** - * @param {AbstractDelta} other + * @param {AbstractArrayDelta} other * @return {boolean} */ equals (other) { @@ -254,13 +351,331 @@ export class AbstractDelta { } /** - * @param {AbstractDelta} other + * @param {AbstractArrayDelta} other */ [traits.EqualityTraitSymbol] (other) { return fun.equalityDeep(this.ops, other.ops) } } +/** + * @template {object} Vals + * @template {keyof Vals} K + * @template {Delta|undefined} Modifiers + * @typedef {(change:MapDeltaChange,key:K) => void} MapDeltaChangeCallback + */ + +/** + * @template V + */ +class MapInsertOp { + /** + * @param {V} value + * @param {V|undefined} prevValue + * @param {Attribution_?} attribution + */ + constructor (value, prevValue, attribution) { + this.prevValue = prevValue + this.attribution = attribution + this.value = value + } + + get type () { return 'insert' } + + toJSON () { + return { + type: this.type, + value: this.value + } + } +} + +/** + * @template V + */ +class MapDeleteOp { + /** + * @param {V|undefined} prevValue + * @param {Attribution_?} attribution + */ + constructor (prevValue, attribution) { + this.prevValue = prevValue + this.attribution = attribution + } + + get value () { return undefined } + + get type () { return 'delete' } + + toJSON () { + return { + type: 'delete' + } + } +} + +/** + * @template {Delta} Modifiers + */ +class MapModifyOp { + /** + * @param {Modifiers} delta + */ + constructor (delta) { + this.modify = delta + } + + get value () { return undefined } + + get type () { return 'insert' } + + toJSON () { + return { + type: 'modify', + modify: this.modify.toJSON() + } + } +} + +/** + * @template V + * @template {Delta|undefined} Modifiers + * @typedef {MapInsertOp | MapDeleteOp | (Modifiers extends undefined ? never : MapModifyOp)} MapDeltaChange + */ + +/** + * @template {object} Vals + * @template {Delta|undefined} [Modifiers=undefined] + */ +export class MapDelta extends AbstractDelta { + constructor () { + super() + /** + * @type {Map>} + */ + this.changes = map.create() + /** + * @type {Attribution_?} + */ + this.usedAttribution = null + } + + /** + * + * Iterate through the changes. There are two approches to iterate through changes. The + * following two examples achieve the same thing: + * + * @example + * d.forEach((op, index) => { + * if (op instanceof delta.InsertArrayOp) { + * op.insert + * } else if (op instanceof delta.RetainOp ) { + * op.retain + * } else if (op instanceof delta.DeleteOp) { + * op.delete + * } else if (op instanceof delta.ModifyOp) { + * op.modify + * } + * }) + * + * The second approach doesn't require instanceof checks. + * + * @example + * d.forEach(null, + * (insertOp, index) => insertOp.insert, + * (retainOp, index) => insertOp.retain + * (deleteOp, index) => insertOp.delete + * (modifyOp, index) => insertOp.modify + * ) + * + * @param {null|((change:MapDeltaChange,key:keyof Vals)=>void)} changeHandler + * @param {null|((insertOp:MapInsertOp,key:keyof Vals)=>void)} insertHandler + * @param {null|((deleteOp:MapDeleteOp,key:keyof Vals)=>void)} deleteHandler + * @param {null|((modifyOp:(MapModifyOp),key:keyof Vals)=>void)} modifyHandler + */ + forEach (changeHandler = null, insertHandler = null, deleteHandler = null, modifyHandler = null) { + this.changes.forEach((change, key) => { + changeHandler?.(change, key) + switch (change.constructor) { + case MapDeleteOp: + deleteHandler?.(/** @type {MapDeleteOp} */ (change), key) + break + case MapInsertOp: + insertHandler?.(/** @type {MapInsertOp} */ (change), key) + break + case MapModifyOp: + modifyHandler?.(/** @type {MapModifyOp} */ (change), key) + break + } + }) + } + + /** + * @template {keyof Vals} K + * + * @param {K} key + * @return {MapDeltaChange | undefined} + */ + get (key) { + return /** @type {MapDeltaChange | undefined} */ (this.changes.get(key)) + } + + /** + * @param {keyof Vals} key + */ + has (key) { + return this.changes.has(key) + } + + /** + * @param {MapDelta} other + * @return {boolean} + */ + equals (other) { + return this[traits.EqualityTraitSymbol](other) + } + + /** + * @return {object} + */ + toJSON () { + /** + * @type {any} + */ + const changes = {} + this.changes.forEach((change, key) => { + changes[key] = change.toJSON() + }) + return changes + } + + /** + * Preferred way to iterate through changes. + * + * @return {IterableIterator<{ [K in keyof Vals]: [K, MapDeltaChange] }[keyof Vals]>} + */ + [Symbol.iterator] () { + // @ts-ignore + return this.changes.entries() + } + + /** + * @param {MapDelta} other + */ + [traits.EqualityTraitSymbol] (other) { + return fun.equalityDeep(this.changes, other.changes) + } + + /** + * @return {MapDelta} + */ + done () { + return this + } +} + +/** + * @template {string|undefined} NodeName + * @template Children + * @template {object} Attrs + * @template {Delta|undefined} [ChildModifiers=undefined] + * @template {Delta|undefined} [AttrModifiers=undefined] + * @template {'done'|'mutable'} [Done='mutable'] + */ +export class XmlDelta extends AbstractDelta { + /** + * @param {NodeName} nodeName + * @param {ArrayDelta} children + * @param {MapDelta} attributes + */ + constructor (nodeName, children = createArrayDelta(), attributes = /** @type {any} */ (createMapDelta())) { + super() + this.nodeName = nodeName + /** + * @type {ArrayDelta} + */ + this.children = children + /** + * @type {Done extends 'mutable' ? MapDeltaBuilder : MapDelta} + */ + this.attributes = /** @type {any} */ (attributes) + } + + toJSON () { + return { + nodeName: this.nodeName, + children: this.children.toJSON(), + attributes: this.attributes.toJSON() + } + } + + /** + * @return {XmlDelta} + */ + done () { + this.children.done() + this.attributes.done() + return /** @type {any} */ (this) + } +} + +/** + * @param {string|undefined} nodeName + */ +export const createXmlDelta = (nodeName = undefined) => new XmlDelta(nodeName) + +/** + * @template {object} Vals + * @template {Delta|undefined} [Modifiers=undefined] + * @extends MapDelta + */ +export class MapDeltaBuilder extends MapDelta { + /** + * @template {keyof Vals} K + * @param {K} key + * @param {Modifiers} delta + */ + modify (key, delta) { + this.changes.set(key, /** @type {any} */ ({ type: 'modify', delta })) + return this + } + + /** + * @template {keyof Vals} K + * @param {K} key + * @param {Vals[K]} newVal + * @param {Vals[K]|undefined} prevValue + * @param {Attribution_?} attribution + */ + set (key, newVal, prevValue = undefined, attribution = null) { + const mergedAttribution = mergeAttrs(this.usedAttribution, attribution) + this.changes.set(key, new MapInsertOp(newVal, prevValue, mergedAttribution)) + return this + } + + /** + * @template {keyof Vals} K + * @param {K} key + * @param {Vals[K]|undefined} prevValue + * @param {Attribution_?} attribution + */ + delete (key, prevValue = undefined, attribution = null) { + const mergedAttribution = mergeAttrs(this.usedAttribution, attribution) + this.changes.set(key, new MapDeleteOp(prevValue, mergedAttribution)) + return this + } + + /** + * @param {Attribution_?} attribution + */ + useAttribution (attribution) { + this.usedAttribution = attribution + return this + } +} + +export const createMapDelta = () => new MapDeltaBuilder() + /** * Helper function to merge attribution and attributes. The latter input "wins". * @@ -272,10 +687,11 @@ const mergeAttrs = (a, b) => object.isEmpty(a) ? b : (object.isEmpty(b) ? a : ob /** * @template {'array' | 'text' | 'custom'} Type - * @template {DeltaOp} TDeltaOp - * @extends AbstractDelta + * @template {DeltaOp} TDeltaOp + * @template {Delta|undefined} Modifiers + * @extends AbstractArrayDelta */ -export class DeltaBuilder extends AbstractDelta { +export class DeltaBuilder extends AbstractArrayDelta { /** * @param {Type} type */ @@ -420,7 +836,8 @@ export class DeltaBuilder extends AbstractDelta { /** * @template {any} ArrayContent - * @extends DeltaBuilder<'array', ArrayDeltaOp>> + * @template {Delta|undefined} Modifiers + * @extends DeltaBuilder<'array', ArrayDeltaOp,Modifiers> */ export class ArrayDelta extends DeltaBuilder { constructor () { @@ -430,7 +847,8 @@ export class ArrayDelta extends DeltaBuilder { /** * @template {object} Embeds - * @extends DeltaBuilder<'text',TextDeltaOp> + * @template {Delta|undefined} [Modifiers=undefined] + * @extends DeltaBuilder<'text',TextDeltaOp,Modifiers> */ export class TextDelta extends DeltaBuilder { constructor () { @@ -440,24 +858,28 @@ export class TextDelta extends DeltaBuilder { /** * @template {'text'|'array'|'custom'} Type - * @template {DeltaOp} DeltaOps - * @typedef {AbstractDelta} DeltaReadonly + * @template {DeltaOp} DeltaOps + * @template {Delta|undefined} Modifiers + * @typedef {AbstractArrayDelta} DeltaReadonly */ /** * @template {object} Embeds - * @typedef {DeltaReadonly<'text',TextDeltaOp>} TextDeltaReadonly + * @template {Delta|undefined} Modifiers + * @typedef {DeltaReadonly<'text',TextDeltaOp,Modifiers>} TextDeltaReadonly */ /** * @template {object} Embeds - * @return {TextDelta} + * @template {Delta|undefined} Modifiers + * @return {TextDelta} */ export const createTextDelta = () => new TextDelta() /** * @template [V=any] - * @return {ArrayDelta} + * @template {Delta|undefined} [Modifiers=undefined] + * @return {ArrayDelta} */ export const createArrayDelta = () => new ArrayDelta() diff --git a/tests/compatibility.tests.js b/tests/compatibility.tests.js index 1155818e..127e7570 100644 --- a/tests/compatibility.tests.js +++ b/tests/compatibility.tests.js @@ -41,5 +41,5 @@ export const testTextDecodingCompatibilityV1 = _tc => { const oldVal = [{"insert":"1306rup"},{"insert":"uj","attributes":{"italic":true,"color":"#888"}},{"insert":"ikkcjnrcpsckw1319bccgkp\n"},{"insert":"\n1131","attributes":{"bold":true}},{"insert":"1326rpcznqahopcrtd","attributes":{"italic":true}},{"insert":"3axhkthhu","attributes":{"bold":true}},{"insert":"28"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"9"},{"insert":"04ku","attributes":{"italic":true}},{"insert":"1323nucvxsqlznwlfavmpc\nu"},{"insert":"tc","attributes":{"italic":true}},{"insert":"je1318jwskjabdndrdlmjae\n1293tj\nj1292qrmf"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"k\nuf"},{"insert":"14hs","attributes":{"italic":true}},{"insert":"13dccxdyxg"},{"insert":"zc","attributes":{"italic":true,"color":"#888"}},{"insert":"apo"},{"insert":"tn","attributes":{"bold":true}},{"insert":"r"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"gn\n"},{"insert":"z","attributes":{"italic":true}},{"insert":"\n121"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"291311kk9zjznywohpx"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"cnbrcaq\n"},{"insert":"1","attributes":{"italic":true,"color":"#888"}},{"insert":"1310g"},{"insert":"ws","attributes":{"italic":true,"color":"#888"}},{"insert":"hxwych"},{"insert":"kq","attributes":{"italic":true}},{"insert":"sdru1320cohbvcrkrpjngdoc\njqic\n"},{"insert":"2","attributes":{"italic":true,"color":"#888"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"90n1297zm"},{"insert":"v1309zlgvjx","attributes":{"bold":true}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"g","attributes":{"bold":true}},{"insert":"1314pycavu","attributes":{"italic":true,"color":"#888"}},{"insert":"pkzqcj"},{"insert":"sa","attributes":{"italic":true,"color":"#888"}},{"insert":"sjy\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"xr\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1295qfrvlyfap201312qrwt"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"b1322rnbaokorixenvp\nrxq"},{"insert":"j","attributes":{"italic":true}},{"insert":"x","attributes":{"italic":true,"color":"#888"}},{"insert":"15mziwabzkrrmscvdovao\n0","attributes":{"italic":true}},{"insert":"hx","attributes":{"italic":true,"bold":true}},{"insert":"ojeetrjhxkr13031317pfcyhksrkpkt\nuhv1","attributes":{"italic":true}},{"insert":"32","attributes":{"italic":true,"color":"#888"}},{"insert":"4rorywthq1325iodbzizxhmlibvpyrxmq\n\nganln\nqne\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"dvf"},{"insert":"ac","attributes":{"bold":true}},{"insert":"1302xciwa"},{"insert":"1305rl","attributes":{"bold":true}},{"insert":"08\n"},{"insert":"eyk","attributes":{"bold":true}},{"insert":"y1321apgivydqsjfsehhezukiqtt1307tvjiejlh"},{"insert":"1316zlpkmctoqomgfthbpg","attributes":{"bold":true}},{"insert":"gv"},{"insert":"lb","attributes":{"bold":true}},{"insert":"f\nhntk\njv1uu\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}}] const doc = new Y.Doc() Y.applyUpdate(doc, buffer.fromBase64(oldDoc)) - t.compare(doc.getText('text').getContent().toJSON(), oldVal) + t.compare(doc.getText('text').getDelta().toJSON(), oldVal) } diff --git a/tests/delta.tests.js b/tests/delta.tests.js index d7de0eb7..14df1edf 100644 --- a/tests/delta.tests.js +++ b/tests/delta.tests.js @@ -1,5 +1,6 @@ import * as t from 'lib0/testing' import * as delta from '../src/utils/Delta.js' +import * as Y from 'yjs' /** * @param {t.TestCase} _tc @@ -77,3 +78,91 @@ export const testUseAttribution = _tc => { .done() t.compare(d, d2) } + +/** + * @param {t.TestCase} _tc + */ +export const testMapDelta = _tc => { + const d = /** @type {delta.MapDeltaBuilder<{ key: string, v: number, over: string }>} */ (delta.createMapDelta()) + d.set('key', 'value') + .useAttribution({ delete: ['me'] }) + .delete('v', 94) + .useAttribution(null) + .set('over', 'writeme', 'i existed before') + .set('over', 'andout') + .done() + t.compare(d.toJSON(), { + key: { type: 'insert', value: 'value', prevValue: undefined, attribution: null }, + v: { type: 'delete', value: undefined, prevValue: 94, attribution: { delete: ['me'] } }, + over: { type: 'insert', value: 'andout', prevValue: 'i existed before', attribution: null } + }) + t.compare(d.origin, null) + t.compare(d.remote, false) + t.compare(d.isDiff, true) + d.forEach((change, key) => { + if (key === 'v') { + t.assert(d.get(key)?.prevValue === 94) // should know that value is number + t.assert(change.prevValue === 94) + } else if (key === 'key') { + t.assert(d.get(key)?.value === 'value') // show know that value is a string + t.assert(change.value === 'value') + } else if (key === 'over') { + t.assert(change.value === 'andout') + } else { + throw new Error() + } + }) + for (const [key, change] of d) { + if (key === 'v') { + t.assert(d.get(key)?.prevValue === 94) + t.assert(change.prevValue === 94) // should know that value is number + } else if (key === 'key') { + t.assert(change.value === 'value') // should know that value is number + } else if (key === 'over') { + t.assert(change.value === 'andout') + } else { + throw new Error() + } + } +} + +/** + * @param {t.TestCase} _tc + */ +export const testXmlDelta = _tc => { + const d = /** @type {delta.XmlDelta} */ (delta.createXmlDelta()) + d.children.insert(['hi']) + d.attributes.set('a', 1) + d.attributes.delete('a', 1) + /** + * @type {Array| number>} + */ + const arr = [] + d.children.forEach( + (op, index) => { + if (op instanceof delta.InsertArrayOp) { + arr.push(op.insert, index) + } + }, + (op, index) => { + arr.push(op.insert, index) + }, + (op, index) => { + arr.push(op.retain) + }, + (op, index) => { + arr.push(op.delete) + } + ) + t.compare(arr, [['hi'], 0, ['hi'], 0]) + const x = d.done() + console.log(x) +} + +/** + * @param {t.TestCase} tc + */ +export const testTextModifyingDelta = tc => { + const d = /** @type {delta.TextDelta|Y.Array>} */ (delta.createTextDelta()).insert('hi').insert(new Y.Map()).done() + console.log(d) +} diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 0e833d19..9c71d1d7 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -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.fromJSON([ + t.compare(content.getDelta(), delta.fromJSON([ { attributes: { type: 'text' }, insert: 'Merge Test' @@ -94,11 +94,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.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) undoManager.undo() - t.compare(text0.getContent(), delta.fromJSON([{ insert: 'bcxyz' }])) + t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'bcxyz' }])) undoManager.redo() - t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) + t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) } /** @@ -694,8 +694,8 @@ export const testUndoDeleteTextFormat = _tc => { }, { insert: ' off the shoulder of Orion.' } ]) - t.compare(text.getContent(), expect) - t.compare(text2.getContent(), expect) + t.compare(text.getDelta(), expect) + t.compare(text2.getDelta(), expect) } /** diff --git a/tests/updates.tests.js b/tests/updates.tests.js index f68c1978..6b3075a9 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -126,7 +126,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.getDelta().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }]) compare(users) } @@ -330,7 +330,7 @@ export const testObfuscateUpdates = _tc => { const omap = odoc.getMap('map') const oarray = odoc.getArray('array') // test ytext - const delta = /** @type {Array} */ (otext.getContent().toJSON()) + const delta = /** @type {Array} */ (otext.getDelta().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) diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 4eaab5d2..b41d9e0b 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -529,7 +529,7 @@ export const testAttributedContent = _tc => { yarray.insert(1, [42]) }) const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] }) - const attributedContent = yarray.getContent(attributionManager) + const attributedContent = yarray.getDelta(attributionManager) console.log(attributedContent.toJSON()) t.assert(attributedContent.equals(expectedContent)) }) diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index ff056e9e..bdfe8704 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -631,21 +631,21 @@ export const testAttributedContent = _tc => { t.group('initial value', () => { ymap.set('test', 42) const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } } - const attributedContent = ymap.getContent(attributionManager) + const attributedContent = ymap.getDelta(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) t.group('overwrite value', () => { ymap.set('test', 'fourtytwo') const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } } - const attributedContent = ymap.getContent(attributionManager) + const attributedContent = ymap.getDelta(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) t.group('delete value', () => { ymap.delete('test') const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } } - const attributedContent = ymap.getContent(attributionManager) + const attributedContent = ymap.getDelta(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index f3587e3d..72b9ec32 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -232,7 +232,7 @@ export const testDeltaBug = _tc => { } ] ytext.applyDelta(addingList) - const result = ytext.getContent() + const result = ytext.getDelta() const expectedResult = delta.createTextDelta() .insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' }) .insert('\n\n\n', { 'table-col': { width: '150' } }) @@ -1589,7 +1589,7 @@ export const testDeltaBug2 = _tc => { } ] ytext.applyDelta(changeEvent) - const delta = ytext.getContent() + const delta = ytext.getDelta() t.compare(delta.ops[40].toJSON(), { insert: '\n', attributes: { @@ -1670,29 +1670,29 @@ export const testBasicFormat = tc => { }) text0.insert(0, 'abc', { bold: true }) t.assert(text0.toString() === 'abc', 'Basic insert with attributes works') - t.compare(text0.getContent(), delta.createTextDelta().insert('abc', { bold: true }).done()) + t.compare(text0.getDelta(), delta.createTextDelta().insert('abc', { bold: true }).done()) t.compare(eventDelta, delta.createTextDelta().insert('abc', { bold: true })) text0.delete(0, 1) t.assert(text0.toString() === 'bc', 'Basic delete on formatted works (position 0)') - t.compare(text0.getContent(), delta.createTextDelta().insert('bc', { bold: true })) + t.compare(text0.getDelta(), delta.createTextDelta().insert('bc', { bold: true })) t.compare(eventDelta, delta.createTextDelta().delete(1)) text0.delete(1, 1) t.assert(text0.toString() === 'b', 'Basic delete works (position 1)') - t.compare(text0.getContent(), delta.createTextDelta().insert('b', { bold: true })) + t.compare(text0.getDelta(), delta.createTextDelta().insert('b', { bold: true })) t.compare(eventDelta, delta.createTextDelta().retain(1).delete(1)) text0.insert(0, 'z', { bold: true }) t.assert(text0.toString() === 'zb') - t.compare(text0.getContent(), delta.createTextDelta().insert('zb', { bold: true })) + t.compare(text0.getDelta(), delta.createTextDelta().insert('zb', { bold: true })) t.compare(eventDelta, delta.createTextDelta().insert('z', { bold: true })) // @ts-ignore t.assert(text0._start.right.right.right.content.str === 'b', 'Does not insert duplicate attribute marker') text0.insert(0, 'y') t.assert(text0.toString() === 'yzb') - t.compare(text0.getContent(), delta.createTextDelta().insert('y').insert('zb', { bold: true })) + t.compare(text0.getDelta(), delta.createTextDelta().insert('y').insert('zb', { bold: true })) t.compare(eventDelta, delta.createTextDelta().insert('y')) text0.format(0, 2, { bold: null }) t.assert(text0.toString() === 'yzb') - t.compare(text0.getContent(), delta.createTextDelta().insert('yz').insert('b', { bold: true })) + t.compare(text0.getDelta(), delta.createTextDelta().insert('yz').insert('b', { bold: true })) t.compare(eventDelta, delta.createTextDelta().retain(1).retain(1, { bold: null })) compare(users) } @@ -1707,13 +1707,13 @@ export const testFalsyFormats = tc => { delta = event.delta.toJSON() }) text0.insert(0, 'abcde', { falsy: false }) - t.compare(text0.getContent().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }]) + t.compare(text0.getDelta().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }]) t.compare(delta, [{ insert: 'abcde', attributes: { falsy: false } }]) text0.format(1, 3, { falsy: true }) - t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }]) + t.compare(text0.getDelta().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }]) t.compare(delta, [{ retain: 1 }, { retain: 3, attributes: { falsy: true } }]) text0.format(2, 1, { falsy: false }) - t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }]) + t.compare(text0.getDelta().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }]) t.compare(delta, [{ retain: 2 }, { retain: 1, attributes: { falsy: false } }]) compare(users) } @@ -1732,7 +1732,7 @@ export const testMultilineFormat = _tc => { { retain: 1 }, // newline character { retain: 10, attributes: { bold: true } } ]) - t.compare(testText.getContent().toJSON(), [ + t.compare(testText.getDelta().toJSON(), [ { insert: 'Test', attributes: { bold: true } }, { insert: '\n' }, { insert: 'Multi-line', attributes: { bold: true } }, @@ -1753,7 +1753,7 @@ export const testNotMergeEmptyLinesFormat = _tc => { { insert: '\nText' }, { insert: '\n', attributes: { title: true } } ]) - t.compare(testText.getContent().toJSON(), [ + t.compare(testText.getDelta().toJSON(), [ { insert: 'Text' }, { insert: '\n', attributes: { title: true } }, { insert: '\nText' }, @@ -1777,7 +1777,7 @@ export const testPreserveAttributesThroughDelete = _tc => { { delete: 1 }, { retain: 1, attributes: { title: true } } ]) - t.compare(testText.getContent().toJSON(), [ + t.compare(testText.getDelta().toJSON(), [ { insert: 'Text' }, { insert: '\n', attributes: { title: true } } ]) @@ -1791,7 +1791,7 @@ export const testGetDeltaWithEmbeds = tc => { text0.applyDelta([{ insert: { linebreak: 's' } }]) - t.compare(text0.getContent().toJSON(), [{ + t.compare(text0.getDelta().toJSON(), [{ insert: { linebreak: 's' } }]) } @@ -1804,7 +1804,7 @@ export const testTypesAsEmbed = tc => { text0.applyDelta([{ insert: new Y.Map([['key', 'val']]) }]) - t.compare(/** @type {delta.InsertEmbedOp} */ (text0.getContent().ops[0]).insert.toJSON(), { key: 'val' }) + t.compare(/** @type {delta.InsertEmbedOp} */ (text0.getDelta().ops[0]).insert.toJSON(), { key: 'val' }) let firedEvent = false text1.observe(event => { const d = event.delta @@ -1813,7 +1813,7 @@ export const testTypesAsEmbed = tc => { firedEvent = true }) testConnector.flushAllMessages() - const delta = text1.getContent().toJSON() + const delta = text1.getDelta().toJSON() t.assert(delta.length === 1) t.compare(/** @type {any} */ (delta[0]).insert.toJSON(), { key: 'val' }) t.assert(firedEvent, 'fired the event observer containing a Type-Embed') @@ -1847,11 +1847,11 @@ export const testSnapshot = tc => { }, { delete: 1 }]) - const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) + const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1)) t.compare(state1.toJSON(), [{ insert: 'abcd' }]) - const state2 = text0.getContent(createAttributionManagerFromSnapshots(snapshot2)) + const state2 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot2)) t.compare(state2.toJSON(), [{ insert: 'axcd' }]) - const state2Diff = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON() + const state2Diff = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON() const expected = [{ insert: 'a' }, { insert: 'x', attribution: { insert: [] } }, { insert: 'b', attribution: { delete: [] } }, { insert: 'cd' }] t.compare(state2Diff, expected) } @@ -1872,7 +1872,7 @@ export const testSnapshotDeleteAfter = tc => { }, { insert: 'e' }]) - const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) + const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1)) t.compare(state1, delta.fromJSON([{ insert: 'abcd' }])) } @@ -1892,7 +1892,7 @@ export const testToDeltaEmbedAttributes = tc => { const { text0 } = init(tc, { users: 1 }) text0.insert(0, 'ab', { bold: true }) text0.insertEmbed(1, { image: 'imageSrc.png' }, { width: 100 }) - const delta0 = text0.getContent().toJSON() + const delta0 = text0.getDelta().toJSON() t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }]) } @@ -1903,7 +1903,7 @@ export const testToDeltaEmbedNoAttributes = tc => { const { text0 } = init(tc, { users: 1 }) text0.insert(0, 'ab', { bold: true }) text0.insertEmbed(1, { image: 'imageSrc.png' }) - const delta0 = text0.getContent().toJSON() + const delta0 = text0.getDelta().toJSON() t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' } }, { insert: 'b', attributes: { bold: true } }], 'toDelta does not set attributes key when no attributes are present') } @@ -2211,9 +2211,9 @@ export const testFormattingBug = async _tc => { { insert: '\n', attributes: { url: 'http://docs.yjs.dev' } }, { insert: '\n', attributes: { url: 'http://example.com' } } ] - t.compare(text1.getContent().toJSON(), expectedResult) - t.compare(text1.getContent().toJSON(), text2.getContent().toJSON()) - console.log(text1.getContent().toJSON()) + t.compare(text1.getDelta().toJSON(), expectedResult) + t.compare(text1.getDelta().toJSON(), text2.getDelta().toJSON()) + console.log(text1.getDelta().toJSON()) } /** @@ -2241,8 +2241,8 @@ export const testDeleteFormatting = _tc => { { insert: 'on ', attributes: { bold: true } }, { insert: 'fire off the shoulder of Orion.' } ] - t.compare(text.getContent().toJSON(), expected) - t.compare(text2.getContent().toJSON(), expected) + t.compare(text.getDelta().toJSON(), expected) + t.compare(text2.getDelta().toJSON(), expected) } /** @@ -2261,14 +2261,14 @@ export const testAttributedContent = _tc => { t.group('insert / delete / format', () => { ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }]) const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!') - const attributedContent = ytext.getContent(attributionManager) + const attributedContent = ytext.getDelta(attributionManager) console.log(attributedContent.toJSON()) t.assert(attributedContent.equals(expectedContent)) }) t.group('unformat', () => { ytext.applyDelta([{ retain: 5, attributes: { italic: null } }]) const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!') - const attributedContent = ytext.getContent(attributionManager) + const attributedContent = ytext.getDelta(attributionManager) console.log(attributedContent.toJSON()) t.assert(attributedContent.equals(expectedContent)) }) @@ -2299,7 +2299,7 @@ export const testAttributedDiffing = _tc => { // implementations is the TwosetAttributionManager const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions) // we render the attributed content with the attributionManager - const attributedContent = ytext.getContent(attributionManager) + const attributedContent = ytext.getDelta(attributionManager) console.log(JSON.stringify(attributedContent.toJSON(), null, 2)) 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)) @@ -2583,7 +2583,7 @@ export const testAttributionManagerDefaultPerformance = tc => { }) t.measureTime(`getContent(attributionManager) performance `, () => { for (let i = 0; i < M; i++) { - ytext.getContent() + ytext.getDelta() } }) } diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index 44a4eb84..9d504cd1 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -207,7 +207,7 @@ export const testFormattingBug = _tc => { { insert: 'C', attributes: { em: {}, strong: {} } } ] yxml.applyDelta(delta) - t.compare(yxml.getContent().toJSON(), delta) + t.compare(yxml.getDelta().toJSON(), delta) } /** @@ -244,10 +244,10 @@ export const testFragmentAttributedContent = _tc => { yfragment.insert(1, [elem3]) }) const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) - const attributedContent = yfragment.getContent(attributionManager) + const attributedContent = yfragment.getDelta(attributionManager) console.log(attributedContent.children.toJSON()) t.assert(attributedContent.children.equals(expectedContent)) - t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) + t.compare(elem1.getDelta(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) }) } @@ -273,7 +273,7 @@ export const testElementAttributedContent = _tc => { yelement.setAttribute('key', '42') }) const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) - const attributedContent = yelement.getContent(attributionManager) + const attributedContent = yelement.getDelta(attributionManager) console.log('children', attributedContent.children.toJSON()) console.log('attributes', attributedContent.attributes) t.assert(attributedContent.children.equals(expectedContent))