From e6ab2bbc127a37cdc64916218e1b61409d012fcb Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sat, 19 Jul 2025 16:17:05 +0200 Subject: [PATCH] major update on the (nested) event system. created dedicated delta classes to represent changes and content representations on all types. --- package-lock.json | 2 +- package.json | 2 +- src/types/AbstractType.js | 66 +++++------ src/types/YArray.js | 27 +++-- src/types/YMap.js | 4 +- src/types/YText.js | 36 +++--- src/types/YXmlElement.js | 27 +++-- src/types/YXmlFragment.js | 10 +- src/types/YXmlText.js | 4 +- src/utils/AttributionManager.js | 31 +++-- src/utils/Delta.js | 199 ++++++++++++++++++++++++-------- src/utils/YEvent.js | 4 +- src/utils/types.js | 11 ++ test.html | 6 +- tests/attribution.tests.js | 6 +- tests/compatibility.tests.js | 2 +- tests/delta.tests.js | 77 ++++++++++-- tests/undo-redo.tests.js | 21 +--- tests/updates.tests.js | 4 +- tests/y-array.tests.js | 5 +- tests/y-map.tests.js | 23 ++-- tests/y-text.tests.js | 79 +++++++------ tests/y-xml.tests.js | 29 ++--- 23 files changed, 428 insertions(+), 247 deletions(-) create mode 100644 src/utils/types.js diff --git a/package-lock.json b/package-lock.json index 7b34ac9b..d124bcc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "14.0.0-8", "license": "MIT", "dependencies": { - "lib0": "^0.2.107" + "lib0": "^0.2.114" }, "devDependencies": { "@types/node": "^22.14.1", diff --git a/package.json b/package.json index 309db69c..20af3808 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ }, "homepage": "https://docs.yjs.dev", "dependencies": { - "lib0": "^0.2.107" + "lib0": "^0.2.114" }, "devDependencies": { "@y/protocols": "^1.0.6-1", diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index a5e3db83..282704b9 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -21,13 +21,6 @@ import * as error from 'lib0/error' import * as math from 'lib0/math' import * as log from 'lib0/logging' -/** - * @typedef {delta.ArrayDelta|delta.TextDelta|{ children: delta.ArrayDelta> }|{ 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> }|{ 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 */ @@ -264,8 +257,11 @@ export const callTypeObservers = (type, transaction, event) => { } /** - * @template EventType * Abstract Yjs Type class + * + * @template EventType + * @template {import('../utils/Delta.js').Delta} [EventDelta=any] + * @template {import('../utils/Delta.js').Delta} [EventDeltaDeep=any] */ export class AbstractType { constructor () { @@ -303,10 +299,10 @@ export class AbstractType { } /** - * @return {AbstractType|null} + * @return {AbstractType|null} */ get parent () { - return this._item ? /** @type {AbstractType} */ (this._item.parent) : null + return this._item ? /** @type {AbstractType} */ (this._item.parent) : null } /** @@ -325,7 +321,7 @@ export class AbstractType { } /** - * @return {AbstractType} + * @return {AbstractType} */ _copy () { throw error.methodUnimplemented() @@ -336,7 +332,7 @@ export class AbstractType { * * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. * - * @return {AbstractType} + * @return {AbstractType} */ clone () { throw error.methodUnimplemented() @@ -415,15 +411,15 @@ export class AbstractType { /** * @param {AbstractAttributionManager} _am - * @return {any} + * @return {EventDelta} */ - getDelta (_am) { + getContent (_am) { error.methodUnimplemented() } /** * @param {AbstractAttributionManager} _am - * @return {DeepContent} + * @return {EventDeltaDeep} */ getContentDeep (_am) { error.methodUnimplemented() @@ -431,7 +427,7 @@ export class AbstractType { } /** - * @param {AbstractType} type + * @param {AbstractType} type * @param {number} start * @param {number} end * @return {Array} @@ -469,7 +465,7 @@ export const typeListSlice = (type, start, end) => { } /** - * @param {AbstractType} type + * @param {AbstractType} type * @return {Array} * * @private @@ -498,8 +494,10 @@ export const typeListToArray = type => { * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the * attribution `{ isDeleted: true, .. }`. * - * @param {AbstractType} type + * @template {delta.ArrayDelta} TypeDelta + * @param {AbstractType} type * @param {import('../internals.js').AbstractAttributionManager} am + * @return {TypeDelta} * * @private * @function @@ -528,11 +526,11 @@ export const typeListGetContent = (type, am) => { } } } - return d + return /** @type {TypeDelta} */ (d.done()) } /** - * @param {AbstractType} type + * @param {AbstractType} type * @param {Snapshot} snapshot * @return {Array} * @@ -557,7 +555,7 @@ export const typeListToArraySnapshot = (type, snapshot) => { /** * Executes a provided function on once on every element of this YArray. * - * @param {AbstractType} type + * @param {AbstractType} type * @param {function(any,number,any):void} f A function to execute on every element of this YArray. * * @private @@ -580,8 +578,8 @@ export const typeListForEach = (type, f) => { /** * @template C,R - * @param {AbstractType} type - * @param {function(C,number,AbstractType):R} f + * @param {AbstractType} type + * @param {function(C,number,AbstractType):R} f * @return {Array} * * @private @@ -599,7 +597,7 @@ export const typeListMap = (type, f) => { } /** - * @param {AbstractType} type + * @param {AbstractType} type * @return {IterableIterator} * * @private @@ -651,8 +649,8 @@ export const typeListCreateIterator = type => { * Executes a provided function on once on every element of this YArray. * Operates on a snapshotted state of the document. * - * @param {AbstractType} type - * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray. + * @param {AbstractType} type + * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray. * @param {Snapshot} snapshot * * @private @@ -673,7 +671,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => { } /** - * @param {AbstractType} type + * @param {AbstractType} type * @param {number} index * @return {any} * @@ -700,7 +698,7 @@ export const typeListGet = (type, index) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {Item?} referenceItem * @param {Array|Array|boolean|number|null|string|Uint8Array>} content * @@ -768,7 +766,7 @@ const lengthExceeded = () => error.create('Length exceeded!') /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {number} index * @param {Array|Array|number|null|string|Uint8Array>} content * @@ -821,7 +819,7 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => { * the search marker. * * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {Array|Array|number|null|string|Uint8Array>} content * * @private @@ -841,7 +839,7 @@ export const typeListPushGenerics = (transaction, parent, content) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {number} index * @param {number} length * @@ -888,7 +886,7 @@ export const typeListDelete = (transaction, parent, index, length) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {string} key * * @private @@ -903,9 +901,9 @@ export const typeMapDelete = (transaction, parent, key) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {string} key - * @param {Object|number|null|Array|string|Uint8Array|AbstractType} value + * @param {Object|number|null|Array|string|Uint8Array|AbstractType} value * * @private * @function diff --git a/src/types/YArray.js b/src/types/YArray.js index 5a1cfa42..54a10a54 100644 --- a/src/types/YArray.js +++ b/src/types/YArray.js @@ -23,19 +23,28 @@ import { AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' +/** + * + * @template Content + * @template {import('../internals.js').Delta|undefined} Modifiers + * @typedef {import('../internals.js').ArrayDelta} ArrayDelta + */ + import * as delta from '../utils/Delta.js' /** * Event that describes the changes on a YArray - * @template T + * @template {import('../utils/types.js').YValue} T * @extends YEvent> */ export class YArrayEvent extends YEvent {} /** * A shared Array implementation. - * @template T - * @extends AbstractType> + * @template {import('../utils/types.js').YValue} T + * @template {ArrayDelta} [TypeDelta=ArrayDelta] + * @template {T extends AbstractType ? ArrayDelta>|DeepD,DeepD> : ArrayDelta} [EventDeltaDeep=T extends AbstractType ? ArrayDelta>|DeepD,DeepD> : ArrayDelta] + * @extends AbstractType,TypeDelta,EventDeltaDeep> * @implements {Iterable} */ export class YArray extends AbstractType { @@ -54,7 +63,7 @@ export class YArray extends AbstractType { /** * Construct a new YArray containing the specified items. - * @template {Object|Array|number|null|string|Uint8Array} T + * @template {import('../utils/types.js').YValue} T * @param {Array} items * @return {YArray} */ @@ -219,16 +228,16 @@ export class YArray extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').ArrayDelta>} The Delta representation of this type. + * @return {EventDeltaDeep} The Delta representation of this type. * * @public */ getContentDeep (am = noAttributionsManager) { - return this.getDelta(am).map(d => /** @type {any} */ ( + return /** @type {any} */ (this.getContent(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 - )) + ))) } /** @@ -239,11 +248,11 @@ export class YArray extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').ArrayDelta>} The Delta representation of this type. + * @return {TypeDelta} The Delta representation of this type. * * @public */ - getDelta (am = noAttributionsManager) { + getContent (am = noAttributionsManager) { return typeListGetContent(this, am) } diff --git a/src/types/YMap.js b/src/types/YMap.js index fd17252e..7372ba93 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -195,11 +195,11 @@ export class YMap extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {import('../internals.js').AbstractAttributionManager} am - * @return {MapDelta<{[key:string]: MapType}>} The Delta representation of this type. + * @return {MapDelta<{[key:string]: MapType},undefined>} The Delta representation of this type. * * @public */ - getDelta (am) { + getContent (am) { return typeMapGetDelta(this, am) } diff --git a/src/types/YText.js b/src/types/YText.js index 1e26ede2..c2ba4b37 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -672,12 +672,12 @@ export class YTextEvent extends YEvent { } /** - * @type {{added:Set,deleted:Set,keys:Map,delta:delta.TextDelta}} + * @type {{added:Set,deleted:Set,keys:Map,delta:delta.TextDelta}} */ get changes () { if (this._changes === null) { /** - * @type {{added:Set,deleted:Set,keys:Map,delta:delta.TextDelta}} + * @type {{added:Set,deleted:Set,keys:Map,delta:delta.TextDelta}} */ const changes = { keys: this.keys, @@ -692,20 +692,20 @@ export class YTextEvent extends YEvent { /** * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. + * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. * * @public */ getDelta (am = noAttributionsManager) { const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)]) - return this.target.getDelta(am, { itemsToRender, retainDeletes: true }) + return this.target.getContent(am, { itemsToRender, retainDeletes: true }) } /** * Compute the changes in the delta format. * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. * - * @type {delta.TextDelta} + * @type {delta.TextDelta} * * @public */ @@ -789,7 +789,7 @@ export class YText extends AbstractType { * @type {YText} */ const text = new YText() - text.applyDelta(this.getDelta()) + text.applyDelta(this.getContent()) return text } @@ -843,7 +843,7 @@ export class YText extends AbstractType { /** * Apply a {@link Delta} on this shared YText type. * - * @param {Array | delta.Delta} delta The changes to apply on this element. + * @param {Array | delta.TextDelta} delta The changes to apply on this element. * @param {AbstractAttributionManager} am * * @public @@ -851,10 +851,7 @@ export class YText extends AbstractType { applyDelta (delta, am = noAttributionsManager) { if (this.doc !== null) { transact(this.doc, transaction => { - /** - * @type {Array} - */ - const deltaOps = /** @type {Array} */ (/** @type {delta.Delta} */ (delta).ops instanceof Array ? /** @type {delta.Delta} */ (delta).ops : delta) + const deltaOps = /** @type {Array} */ (/** @type {delta.TextDelta} */ (delta).ops instanceof Array ? /** @type {delta.TextDelta} */ (delta).ops : delta) const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am) for (let i = 0; i < deltaOps.length; i++) { const op = deltaOps[i] @@ -882,14 +879,14 @@ export class YText extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').TextDelta< Embeds extends import('./AbstractType.js').AbstractType ? import('./AbstractType.js').DeepContent : Embeds >} The Delta representation of this type. + * @return {import('../utils/Delta.js').TextDelta ? SubEvent : Embeds, undefined>} The Delta representation of this type. * * @public */ getContentDeep (am = noAttributionsManager) { - return this.getDelta(am).map(d => + return this.getContent(am).map(d => d instanceof delta.InsertEmbedOp && d.insert instanceof AbstractType - ? new delta.InsertEmbedOp(d.insert.getDelta(am), d.attributes, d.attribution) + ? new delta.InsertEmbedOp(d.insert.getContent(am), d.attributes, d.attribution) : d ) } @@ -906,13 +903,13 @@ export class YText extends 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 - * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. + * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. * * @public */ - getDelta (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false } = {}) { + getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false } = {}) { /** - * @type {import('../utils/Delta.js').TextDelta} + * @type {import('../utils/Delta.js').TextDeltaBuilder} */ const d = delta.createTextDelta() /** @@ -1108,7 +1105,8 @@ export class YText extends AbstractType { } } } - return d.done() + // @todo! fix the typings here + return /** @type {any} */ (d.done()) } /** @@ -1291,7 +1289,7 @@ export class YText extends AbstractType { * @param {this} other */ [traits.EqualityTraitSymbol] (other) { - return this.getDelta().equals(other.getDelta()) + return this.getContent().equals(other.getContent()) } } diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index 833fbfdf..bd61f21f 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -218,26 +218,33 @@ export class YXmlElement extends YXmlFragment { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {{ nodeName: string, children: delta.ArrayDelta>, attributes: import('./AbstractType.js').MapAttributedContent }} + * @return {{ nodeName: string, children: delta.ArrayDeltaBuilder>, attributes: import('./AbstractType.js').MapAttributedContent }} * * @public */ getContentDeep (am = noAttributionsManager) { - const { children: origChildren, attributes: origAttributes } = this.getDelta(am) + const { children: origChildren, attributes: origAttributes } = this.getContent(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) + ? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDeltaBuilder>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution) : d )) /** * @todo there is a Attributes type and a DeepAttributes type. - * @type {delta.MapDelta<>} + * @type {delta.MapDeltaBuilder} */ 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 }) - }) - return { nodeName: this.nodeName, children, attributes } + origAttributes.forEach( + null, + (insertOp, key) => { + if (insertOp.value instanceof AbstractType) { + attributes.set(key, insertOp.value.getContentDeep(am), null, insertOp.attribution) + } else { + attributes.set(key, insertOp.value, undefined, insertOp.attribution) + } + } + ) + return delta.createXmlDelta(this.nodeName, children, attributes) } /** @@ -251,8 +258,8 @@ export class YXmlElement extends YXmlFragment { * * @public */ - getDelta (am = noAttributionsManager) { - const { children } = super.getDelta(am) + getContent (am = noAttributionsManager) { + const { children } = super.getContent(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 e63e0d8a..403059b6 100644 --- a/src/types/YXmlFragment.js +++ b/src/types/YXmlFragment.js @@ -384,9 +384,9 @@ export class YXmlFragment extends AbstractType { * Calculate the attributed content using the attribution manager. * * @param {import('../internals.js').AbstractAttributionManager} am - * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} + * @return {{ children: import('../utils/Delta.js').ArrayDeltaBuilderBuilder> }} */ - getDelta (am = noAttributionsManager) { + getContent (am = noAttributionsManager) { const children = typeListGetContent(this, am) return { children } } @@ -395,12 +395,12 @@ export class YXmlFragment extends AbstractType { * Calculate the attributed content using the attribution manager. * * @param {import('../internals.js').AbstractAttributionManager} am - * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} + * @return {{ children: import('../utils/Delta.js').ArrayDeltaBuilderBuilder> }} */ getContentDeep (am) { - const { children: origChildren } = this.getDelta(am) + const { children: origChildren } = this.getContent(am) /** - * @type {import('../utils/Delta.js').ArrayDelta>} + * @type {import('../utils/Delta.js').ArrayDeltaBuilderBuilder>} */ const children = origChildren.map(d => /** @type {any} */ ( d instanceof delta.InsertArrayOp && d.insert instanceof Array diff --git a/src/types/YXmlText.js b/src/types/YXmlText.js index 195a1e3d..7b5ac37c 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.getDelta()) + text.applyDelta(this.getContent()) return text } @@ -68,7 +68,7 @@ export class YXmlText extends YText { } toString () { - return this.getDelta().ops.map(dop => { + return this.getContent().ops.map(dop => { if (dop instanceof delta.InsertStringOp) { const nestedNodes = [] for (const nodeName in dop.attributes) { diff --git a/src/utils/AttributionManager.js b/src/utils/AttributionManager.js index a5bb81f2..0dc20073 100644 --- a/src/utils/AttributionManager.js +++ b/src/utils/AttributionManager.js @@ -31,21 +31,21 @@ import { import * as error from 'lib0/error' import { ObservableV2 } from 'lib0/observable' import * as encoding from 'lib0/encoding' +import * as s from 'lib0/schema' + +export const attributionJsonSchema = s.object({ + insert: s.array(s.string).optional, + 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 +}) /** * @todo rename this to `insertBy`, `insertAt`, .. * - * @typedef {Object} Attribution - * @property {Array} [Attribution.insert] - * @property {number} [Attribution.insertedAt] - * @property {Array} [Attribution.acceptInsert] - * @property {number} [Attribution.acceptedDeleteAt] - * @property {Array} [Attribution.acceptDelete] - * @property {number} [Attribution.acceptedDeleteAt] - * @property {Array} [Attribution.delete] - * @property {number} [Attribution.deletedAt] - * @property {{ [key: string]: Array }} [Attribution.attributes] - * @property {number} [Attribution.attributedAt] + * @typedef {s.Unwrap} Attribution */ /** @@ -63,18 +63,13 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => { */ const attribution = {} if (deleted) { - attribution.delete = [] + attribution.delete = s.array(s.string).ensure([]) } else { attribution.insert = [] } attrs.forEach(attr => { switch (attr.name) { - case 'acceptDelete': - delete attribution.delete - // eslint-disable-next-line no-fallthrough - case 'acceptInsert': - delete attribution.insert - // eslint-disable-next-line no-fallthrough + // eslint-disable-next-line no-fallthrough case 'insert': case 'delete': { const as = /** @type {import('../utils/Delta.js').Attribution_} */ (attribution) diff --git a/src/utils/Delta.js b/src/utils/Delta.js index f140b693..4f367246 100644 --- a/src/utils/Delta.js +++ b/src/utils/Delta.js @@ -3,6 +3,8 @@ import * as map from 'lib0/map' import * as fun from 'lib0/function' import * as traits from 'lib0/traits' import * as error from 'lib0/error' +import * as s from 'lib0/schema' +import { attributionJsonSchema } from './AttributionManager.js' /** * @template {any} ArrayContent @@ -50,6 +52,13 @@ export class InsertStringOp { this.attribution = attribution } + /** + * @return {'insert'} + */ + get type () { + return 'insert' + } + get length () { return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1 } @@ -84,6 +93,13 @@ export class InsertArrayOp { this.attribution = attribution } + /** + * @return {'insert'} + */ + get type () { + return 'insert' + } + get length () { return this.insert.length } @@ -118,6 +134,13 @@ export class InsertEmbedOp { this.attribution = attribution } + /** + * @return {'insertEmbed'} + */ + get type () { + return 'insertEmbed' + } + get length () { return 1 } @@ -145,6 +168,13 @@ export class DeleteOp { this.delete = len } + /** + * @return {'delete'} + */ + get type () { + return 'delete' + } + get length () { return 0 } @@ -176,6 +206,13 @@ export class RetainOp { this.attribution = attribution } + /** + * @return {'retain'} + */ + get type () { + return 'retain' + } + get length () { return this.retain } @@ -208,6 +245,13 @@ export class ModifyOp { this.modify = delta } + /** + * @return {'modify'} + */ + get type () { + return 'modify' + } + get length () { return 1 } @@ -247,7 +291,7 @@ export class AbstractDelta { /** * @template {Delta|undefined} [Modifiers=any] - * @typedef {(TextDelta | ArrayDelta | MapDelta | XmlDelta )} Delta + * @typedef {(TextDelta | ArrayDelta | MapDelta | XmlDelta )} Delta */ /** @@ -271,10 +315,10 @@ export class AbstractArrayDelta extends AbstractDelta { /** * @template {(d:TDeltaOp) => DeltaOp} Mapper * @param {Mapper} f - * @return {DeltaBuilder infer OP ? OP : unknown,Modifiers>} + * @return {AbstractArrayDeltaBuilder infer OP ? OP : unknown,Modifiers>} */ map (f) { - const d = /** @type {DeltaBuilder} */ (new /** @type {any} */ (this.constructor)(this.type)) + const d = /** @type {AbstractArrayDeltaBuilder} */ (new /** @type {any} */ (this.constructor)(this.type)) d.ops = this.ops.map(f) // @ts-ignore d.lastOp = d.ops[d.ops.length - 1] ?? null @@ -307,7 +351,7 @@ export class AbstractArrayDelta extends AbstractDelta { * ) * * @param {null|((d:TDeltaOp,index:number)=>void)} f - * @param {null|((insertOp: (InsertEmbedOp | InsertStringOp | InsertArrayOp) & TDeltaOp,index:number)=>void)} insertHandler + * @param {null|((insertOp:Exclude>,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 @@ -380,14 +424,26 @@ class MapInsertOp { this.value = value } + /** + * @return {'insert'} + */ get type () { return 'insert' } toJSON () { return { type: this.type, - value: this.value + value: this.value, + prevValue: this.prevValue, + attribution: this.attribution } } + + /** + * @param {MapInsertOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return fun.equalityDeep(this.value, other.value) && fun.equalityDeep(this.prevValue, other.prevValue) && fun.equalityDeep(this.attribution, other.attribution) + } } /** @@ -405,13 +461,25 @@ class MapDeleteOp { get value () { return undefined } + /** + * @type {'delete'} + */ get type () { return 'delete' } toJSON () { return { - type: 'delete' + type: this.type, + prevValue: this.prevValue, + attribution: this.attribution } } + + /** + * @param {MapDeleteOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return fun.equalityDeep(this.prevValue, other.prevValue) && fun.equalityDeep(this.attribution, other.attribution) + } } /** @@ -427,14 +495,24 @@ class MapModifyOp { get value () { return undefined } - get type () { return 'insert' } + /** + * @type {'modify'} + */ + get type () { return 'modify' } toJSON () { return { - type: 'modify', + type: this.type, modify: this.modify.toJSON() } } + + /** + * @param {MapModifyOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.modify[traits.EqualityTraitSymbol](other.modify) + } } /** @@ -443,9 +521,17 @@ class MapModifyOp { * @typedef {MapInsertOp | MapDeleteOp | (Modifiers extends undefined ? never : MapModifyOp)} MapDeltaChange */ +export const mapDeltaChangeJsonSchema = s.union( + s.object({ type: s.literal('insert'), value: s.any, prevValue: s.any.optional, attribution: attributionJsonSchema.nullable.optional }), + s.object({ type: s.literal('delete'), prevValue: s.any.optional, attribution: attributionJsonSchema.nullable.optional }), + s.object({ type: s.literal('modify'), modify: s.any }) +) + +export const mapDeltaJsonSchema = s.record(s.string, mapDeltaChangeJsonSchema) + /** * @template {object} Vals - * @template {Delta|undefined} [Modifiers=undefined] + * @template {Delta|undefined} Modifiers */ export class MapDelta extends AbstractDelta { constructor () { @@ -528,7 +614,7 @@ export class MapDelta extends AbstractDelta { } /** - * @param {MapDelta} other + * @param {MapDelta} other * @return {boolean} */ equals (other) { @@ -536,15 +622,15 @@ export class MapDelta extends AbstractDelta { } /** - * @return {object} + * @return {s.Unwrap} */ toJSON () { /** - * @type {any} + * @type {s.Unwrap} */ const changes = {} this.changes.forEach((change, key) => { - changes[key] = change.toJSON() + changes[/** @type {string} */ (key)] = change.toJSON() }) return changes } @@ -560,7 +646,7 @@ export class MapDelta extends AbstractDelta { } /** - * @param {MapDelta} other + * @param {MapDelta} other */ [traits.EqualityTraitSymbol] (other) { return fun.equalityDeep(this.changes, other.changes) @@ -585,18 +671,18 @@ export class MapDelta extends AbstractDelta { export class XmlDelta extends AbstractDelta { /** * @param {NodeName} nodeName - * @param {ArrayDelta} children + * @param {ArrayDeltaBuilder} children * @param {MapDelta} attributes */ - constructor (nodeName, children = createArrayDelta(), attributes = /** @type {any} */ (createMapDelta())) { + constructor (nodeName, children, attributes) { super() this.nodeName = nodeName /** - * @type {ArrayDelta} + * @type {ArrayDeltaBuilder} */ this.children = children /** - * @type {Done extends 'mutable' ? MapDeltaBuilder : MapDelta} + * @type {Done extends 'mutable' ? MapDeltaBuilder : MapDelta} */ this.attributes = /** @type {any} */ (attributes) } @@ -617,12 +703,27 @@ export class XmlDelta extends AbstractDelta { this.attributes.done() return /** @type {any} */ (this) } + + /** + * @param {XmlDelta} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.nodeName === other.nodeName && this.children[traits.EqualityTraitSymbol](other.children) && this.attributes[traits.EqualityTraitSymbol](other.attributes) + } } /** - * @param {string|undefined} nodeName + * @template {string|undefined} NodeName + * @template Children + * @template {object} Attrs + * @template {Delta|undefined} [ChildModifiers=undefined] + * @template {Delta|undefined} [AttrModifiers=undefined] + * @param {NodeName} nodeName + * @param {ArrayDeltaBuilder} children + * @param {MapDeltaBuilder} attributes + * @return {XmlDelta} */ -export const createXmlDelta = (nodeName = undefined) => new XmlDelta(nodeName) +export const createXmlDelta = (nodeName, children = createArrayDelta(), attributes = /** @type {any} */ (createMapDelta())) => new XmlDelta(nodeName, children, attributes) /** * @template {object} Vals @@ -691,7 +792,7 @@ const mergeAttrs = (a, b) => object.isEmpty(a) ? b : (object.isEmpty(b) ? a : ob * @template {Delta|undefined} Modifiers * @extends AbstractArrayDelta */ -export class DeltaBuilder extends AbstractArrayDelta { +export class AbstractArrayDeltaBuilder extends AbstractArrayDelta { /** * @param {Type} type */ @@ -823,72 +924,72 @@ export class DeltaBuilder extends AbstractArrayDelta { } /** - * @return {this} + * @return {Type extends 'array' ? ArrayDelta : (Type extends 'text' ? TextDelta : AbstractArrayDelta)} */ done () { while (this.lastOp != null && this.lastOp instanceof RetainOp && this.lastOp.attributes === null && this.lastOp.attribution === null) { this.ops.pop() this.lastOp = this.ops[this.ops.length - 1] ?? null } - return this + return /** @type {any} */ (this) } } /** * @template {any} ArrayContent * @template {Delta|undefined} Modifiers - * @extends DeltaBuilder<'array', ArrayDeltaOp,Modifiers> + * @extends AbstractArrayDeltaBuilder<'array', ArrayDeltaOp,Modifiers> */ -export class ArrayDelta extends DeltaBuilder { +export class ArrayDeltaBuilder extends AbstractArrayDeltaBuilder { constructor () { super('array') } } +/** + * @template {any} ArrayContent + * @template {Delta|undefined} Modifiers + * @typedef {AbstractArrayDelta<'array', ArrayDeltaOp,Modifiers>} ArrayDelta + */ + +/** + * @template {object} Embeds + * @template {Delta|undefined} Modifiers + * @typedef {AbstractArrayDelta<'text',TextDeltaOp,Modifiers>} TextDelta + */ + /** * @template {object} Embeds * @template {Delta|undefined} [Modifiers=undefined] - * @extends DeltaBuilder<'text',TextDeltaOp,Modifiers> + * @extends AbstractArrayDeltaBuilder<'text',TextDeltaOp,Modifiers> */ -export class TextDelta extends DeltaBuilder { +export class TextDeltaBuilder extends AbstractArrayDeltaBuilder { constructor () { super('text') } } /** - * @template {'text'|'array'|'custom'} Type - * @template {DeltaOp} DeltaOps - * @template {Delta|undefined} Modifiers - * @typedef {AbstractArrayDelta} DeltaReadonly + * @template {object} [Embeds=any] + * @template {Delta|undefined} [Modifiers=undefined] + * @return {TextDeltaBuilder} */ - -/** - * @template {object} Embeds - * @template {Delta|undefined} Modifiers - * @typedef {DeltaReadonly<'text',TextDeltaOp,Modifiers>} TextDeltaReadonly - */ - -/** - * @template {object} Embeds - * @template {Delta|undefined} Modifiers - * @return {TextDelta} - */ -export const createTextDelta = () => new TextDelta() +export const createTextDelta = () => new TextDeltaBuilder() /** * @template [V=any] * @template {Delta|undefined} [Modifiers=undefined] - * @return {ArrayDelta} + * @return {ArrayDeltaBuilder} */ -export const createArrayDelta = () => new ArrayDelta() +export const createArrayDelta = () => new ArrayDeltaBuilder() /** + * @template {'custom' | 'text' | 'array'} T * @param {DeltaJson} ops - * @param {'custom' | 'text' | 'array'} type + * @param {T} type */ -export const fromJSON = (ops, type = 'custom') => { - const d = new DeltaBuilder(type) +export const fromJSON = (ops, type) => { + const d = new AbstractArrayDeltaBuilder(type) for (let i = 0; i < ops.length; i++) { const op = /** @type {any} */ (ops[i]) // @ts-ignore diff --git a/src/utils/YEvent.js b/src/utils/YEvent.js index 3827fb18..37c32eae 100644 --- a/src/utils/YEvent.js +++ b/src/utils/YEvent.js @@ -1,5 +1,5 @@ import { - TextDelta, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line + TextDeltaBuilder, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line } from '../internals.js' import * as set from 'lib0/set' @@ -42,7 +42,7 @@ export class YEvent { */ this._keys = null /** - * @type {TextDelta?} + * @type {import('./Delta.js').TextDelta?} */ this._delta = null /** diff --git a/src/utils/types.js b/src/utils/types.js new file mode 100644 index 00000000..0901a6b6 --- /dev/null +++ b/src/utils/types.js @@ -0,0 +1,11 @@ + +/** + * @typedef {Object|Array|number|null|string|Uint8Array|BigInt + * |import('../index.js').Array + * |import('../index.js').Map + * |import('../index.js').Text + * |import('../index.js').XmlElement + * |import('../index.js').XmlFragment + * |import('../index.js').XmlText + * |import('../index.js').XmlHook} YValue + */ diff --git a/test.html b/test.html index fb01fd9d..282340e9 100644 --- a/test.html +++ b/test.html @@ -42,6 +42,7 @@ "lib0/decoding.js": "./node_modules/lib0/decoding.js", "lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs", "lib0/decoding": "./node_modules/lib0/decoding.js", + "lib0/diff/patience": "./node_modules/lib0/diff/patience.js", "lib0/diff.js": "./node_modules/lib0/diff.js", "lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs", "lib0/diff": "./node_modules/lib0/diff.js", @@ -157,6 +158,7 @@ "lib0/performance.js": "./node_modules/lib0/performance.js", "lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs", "lib0/performance": "./node_modules/lib0/performance.js", + "lib0/schema": "./node_modules/lib0/schema.js", "@y/protocols/package.json": "./node_modules/@y/protocols/package.json", "@y/protocols/sync.js": "./node_modules/@y/protocols/sync.js", "@y/protocols/dist/sync.cjs": "./node_modules/@y/protocols/dist/sync.cjs", @@ -206,6 +208,7 @@ "lib0/decoding.js": "./node_modules/lib0/decoding.js", "lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs", "lib0/decoding": "./node_modules/lib0/decoding.js", + "lib0/diff/patience": "./node_modules/lib0/diff/patience.js", "lib0/diff.js": "./node_modules/lib0/diff.js", "lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs", "lib0/diff": "./node_modules/lib0/diff.js", @@ -320,7 +323,8 @@ "lib0/webcrypto": "./node_modules/lib0/webcrypto.js", "lib0/performance.js": "./node_modules/lib0/performance.js", "lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs", - "lib0/performance": "./node_modules/lib0/performance.js" + "lib0/performance": "./node_modules/lib0/performance.js", + "lib0/schema": "./node_modules/lib0/schema.js" } } } diff --git a/tests/attribution.tests.js b/tests/attribution.tests.js index 29e21e4d..13bc896a 100644 --- a/tests/attribution.tests.js +++ b/tests/attribution.tests.js @@ -39,7 +39,7 @@ export const testAttributedEvents = _tc => { ytext.delete(6, 5) }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) - const c1 = ytext.getDelta(am) + const c1 = ytext.getContent(am) t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] })) let calledObserver = false ytext.observe(event => { @@ -63,7 +63,7 @@ export const testInsertionsMindingAttributedContent = _tc => { ytext.delete(6, 5) }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) - const c1 = ytext.getDelta(am) + const c1 = ytext.getContent(am) t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] })) ytext.applyDelta(delta.createTextDelta().retain(11).insert('content'), am) t.assert(ytext.toString() === 'hello content') @@ -81,7 +81,7 @@ export const testInsertionsIntoAttributedContent = _tc => { ytext.insert(6, 'word') }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) - const c1 = ytext.getDelta(am) + const c1 = ytext.getContent(am) t.compare(c1, delta.createTextDelta().insert('hello ').insert('word', null, { insert: [] })) ytext.applyDelta(delta.createTextDelta().retain(9).insert('l'), am) t.assert(ytext.toString() === 'hello world') diff --git a/tests/compatibility.tests.js b/tests/compatibility.tests.js index 127e7570..1155818e 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').getDelta().toJSON(), oldVal) + t.compare(doc.getText('text').getContent().toJSON(), oldVal) } diff --git a/tests/delta.tests.js b/tests/delta.tests.js index 14df1edf..0e036e76 100644 --- a/tests/delta.tests.js +++ b/tests/delta.tests.js @@ -1,6 +1,7 @@ import * as t from 'lib0/testing' import * as delta from '../src/utils/Delta.js' import * as Y from 'yjs' +import * as schema from 'lib0/schema' /** * @param {t.TestCase} _tc @@ -88,12 +89,11 @@ export const testMapDelta = _tc => { .useAttribution({ delete: ['me'] }) .delete('v', 94) .useAttribution(null) - .set('over', 'writeme', 'i existed before') - .set('over', 'andout') + .set('over', 'andout', 'i existed before') .done() t.compare(d.toJSON(), { key: { type: 'insert', value: 'value', prevValue: undefined, attribution: null }, - v: { type: 'delete', value: undefined, prevValue: 94, attribution: { delete: ['me'] } }, + v: { type: 'delete', prevValue: 94, attribution: { delete: ['me'] } }, over: { type: 'insert', value: 'andout', prevValue: 'i existed before', attribution: null } }) t.compare(d.origin, null) @@ -117,7 +117,7 @@ export const testMapDelta = _tc => { 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 + t.assert(change.value === 'value') // should know that value is string } else if (key === 'over') { t.assert(change.value === 'andout') } else { @@ -147,10 +147,10 @@ export const testXmlDelta = _tc => { (op, index) => { arr.push(op.insert, index) }, - (op, index) => { + (op, _index) => { arr.push(op.retain) }, - (op, index) => { + (op, _index) => { arr.push(op.delete) } ) @@ -159,10 +159,69 @@ export const testXmlDelta = _tc => { console.log(x) } +const textDeltaSchema = schema.object({ + ops: schema.array( + schema.any + ) +}) + /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testTextModifyingDelta = tc => { - const d = /** @type {delta.TextDelta|Y.Array>} */ (delta.createTextDelta()).insert('hi').insert(new Y.Map()).done() +export const testTextModifyingDelta = _tc => { + const d = /** @type {delta.TextDelta|Y.Array,undefined>} */ (delta.createTextDelta().insert('hi').insert(new Y.Map()).done()) + schema.assert(d, textDeltaSchema) console.log(d) } + +/** + * @param {t.TestCase} _tc + */ +export const testYtypeDeltaTypings = _tc => { + const ydoc = new Y.Doc({ gc: false }) + { + const yarray = /** @type {Y.Array} */ (ydoc.getArray('numbers')) + const content = yarray.getContent() + content.forEach( + op => { + schema.union( + schema.constructedBy(delta.InsertArrayOp), + schema.constructedBy(delta.RetainOp), + schema.constructedBy(delta.DeleteOp) + ).ensure(op) + }, + op => { + schema.constructedBy(delta.InsertArrayOp).ensure(op) + }, + op => { + schema.constructedBy(delta.RetainOp).ensure(op) + }, + op => { + schema.constructedBy(delta.DeleteOp).ensure(op) + } + ) + const cdeep = yarray.getContentDeep() + cdeep.forEach( + op => { + schema.union( + schema.constructedBy(delta.InsertArrayOp), + schema.constructedBy(delta.RetainOp), + schema.constructedBy(delta.DeleteOp), + schema.constructedBy(delta.ModifyOp) + ).ensure(op) + }, + op => { + schema.constructedBy(delta.InsertArrayOp).ensure(op) + }, + op => { + schema.constructedBy(delta.RetainOp).ensure(op) + }, + op => { + schema.constructedBy(delta.DeleteOp).ensure(op) + }, + op => { + schema.constructedBy(delta.ModifyOp).ensure(op) + } + ) + } +} diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 9c71d1d7..88812af1 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -11,16 +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.getDelta(), delta.fromJSON([ - { - attributes: { type: 'text' }, - insert: 'Merge Test' - }, - { - attributes: { type: 'text', italic: true }, - insert: ' After' - } - ])) + t.compare(content.getContent(), delta.createTextDelta().insert('Merge Test', { type: 'text' }).insert(' After', { type: 'text', italic: true })) } const initializeYDoc = () => { const yDoc = new Y.Doc({ gc: false }) @@ -94,11 +85,11 @@ export const testUndoText = tc => { t.assert(text0.toString() === 'bcxyz') // test marks text0.format(1, 3, { bold: true }) - t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) + t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) undoManager.undo() - t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'bcxyz' }])) + t.compare(text0.getContent(), delta.fromJSON([{ insert: 'bcxyz' }])) undoManager.redo() - t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) + t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])) } /** @@ -694,8 +685,8 @@ export const testUndoDeleteTextFormat = _tc => { }, { insert: ' off the shoulder of Orion.' } ]) - t.compare(text.getDelta(), expect) - t.compare(text2.getDelta(), expect) + t.compare(text.getContent(), expect) + t.compare(text2.getContent(), expect) } /** diff --git a/tests/updates.tests.js b/tests/updates.tests.js index 6b3075a9..f68c1978 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.getDelta().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }]) + t.compare(text1.getContent().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.getDelta().toJSON()) + const delta = /** @type {Array} */ (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) diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index b41d9e0b..7a31f632 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -515,6 +515,9 @@ export const testIteratingArrayContainingTypes = _tc => { */ export const testAttributedContent = _tc => { const ydoc = new Y.Doc({ gc: false }) + /** + * @type {Y.Array} + */ const yarray = ydoc.getArray() yarray.insert(0, [1, 2]) let attributionManager = Y.noAttributionsManager @@ -529,7 +532,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.getDelta(attributionManager) + const attributedContent = yarray.getContent(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 bdfe8704..db89f575 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -4,7 +4,8 @@ import { compareIDs, noAttributionsManager, TwosetAttributionManager, - createIdMapFromIdSet + createIdMapFromIdSet, + mapDeltaJsonSchema } from '../src/internals.js' import * as t from 'lib0/testing' import * as prng from 'lib0/prng' @@ -630,24 +631,24 @@ export const testAttributedContent = _tc => { }) t.group('initial value', () => { ymap.set('test', 42) - const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } } - const attributedContent = ymap.getDelta(attributionManager) - console.log(attributedContent) - t.compare(expectedContent, attributedContent) + const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: undefined, value: 42, attribution: { insert: [] } } }) + const attributedContent = ymap.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.compare(expectedContent, attributedContent.toJSON()) }) t.group('overwrite value', () => { ymap.set('test', 'fourtytwo') - const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } } - const attributedContent = ymap.getDelta(attributionManager) + const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } }) + const attributedContent = ymap.getContent(attributionManager) console.log(attributedContent) - t.compare(expectedContent, attributedContent) + t.compare(expectedContent, attributedContent.toJSON()) }) t.group('delete value', () => { ymap.delete('test') - const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } } - const attributedContent = ymap.getDelta(attributionManager) + const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'delete', prevValue: 'fourtytwo', attribution: { delete: [] } } }) + const attributedContent = ymap.getContent(attributionManager) console.log(attributedContent) - t.compare(expectedContent, attributedContent) + t.compare(expectedContent, attributedContent.toJSON()) }) } diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index 72b9ec32..db2db6b0 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -232,7 +232,10 @@ export const testDeltaBug = _tc => { } ] ytext.applyDelta(addingList) - const result = ytext.getDelta() + const result = ytext.getContent() + /** + * @type {delta.TextDelta} + */ const expectedResult = delta.createTextDelta() .insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' }) .insert('\n\n\n', { 'table-col': { width: '150' } }) @@ -1589,7 +1592,7 @@ export const testDeltaBug2 = _tc => { } ] ytext.applyDelta(changeEvent) - const delta = ytext.getDelta() + const delta = ytext.getContent() t.compare(delta.ops[40].toJSON(), { insert: '\n', attributes: { @@ -1640,21 +1643,21 @@ export const testBasicInsertAndDelete = tc => { text0.insert(0, 'abc') t.assert(text0.toString() === 'abc', 'Basic insert works') - t.compare(eventDelta, delta.fromJSON([{ insert: 'abc' }])) + t.compare(eventDelta, delta.createTextDelta().insert('abc')) text0.delete(0, 1) t.assert(text0.toString() === 'bc', 'Basic delete works (position 0)') - t.compare(eventDelta, delta.fromJSON([{ delete: 1 }])) + t.compare(eventDelta, delta.createTextDelta().delete(1)) text0.delete(1, 1) t.assert(text0.toString() === 'b', 'Basic delete works (position 1)') - t.compare(eventDelta, delta.fromJSON([{ retain: 1 }, { delete: 1 }])) + t.compare(eventDelta, delta.createTextDelta().retain(1).delete(1)) users[0].transact(() => { text0.insert(0, '1') text0.delete(0, 1) }) - t.compare(eventDelta, delta.fromJSON([])) + t.compare(eventDelta, delta.createTextDelta()) compare(users) } @@ -1670,29 +1673,29 @@ export const testBasicFormat = tc => { }) text0.insert(0, 'abc', { bold: true }) t.assert(text0.toString() === 'abc', 'Basic insert with attributes works') - t.compare(text0.getDelta(), delta.createTextDelta().insert('abc', { bold: true }).done()) + t.compare(text0.getContent(), 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.getDelta(), delta.createTextDelta().insert('bc', { bold: true })) + t.compare(text0.getContent(), 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.getDelta(), delta.createTextDelta().insert('b', { bold: true })) + t.compare(text0.getContent(), 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.getDelta(), delta.createTextDelta().insert('zb', { bold: true })) + t.compare(text0.getContent(), 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.getDelta(), delta.createTextDelta().insert('y').insert('zb', { bold: true })) + t.compare(text0.getContent(), 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.getDelta(), delta.createTextDelta().insert('yz').insert('b', { bold: true })) + t.compare(text0.getContent(), delta.createTextDelta().insert('yz').insert('b', { bold: true })) t.compare(eventDelta, delta.createTextDelta().retain(1).retain(1, { bold: null })) compare(users) } @@ -1707,13 +1710,13 @@ export const testFalsyFormats = tc => { delta = event.delta.toJSON() }) text0.insert(0, 'abcde', { falsy: false }) - t.compare(text0.getDelta().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }]) + t.compare(text0.getContent().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }]) t.compare(delta, [{ insert: 'abcde', attributes: { falsy: false } }]) text0.format(1, 3, { falsy: true }) - t.compare(text0.getDelta().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }]) + t.compare(text0.getContent().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.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(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(delta, [{ retain: 2 }, { retain: 1, attributes: { falsy: false } }]) compare(users) } @@ -1732,7 +1735,7 @@ export const testMultilineFormat = _tc => { { retain: 1 }, // newline character { retain: 10, attributes: { bold: true } } ]) - t.compare(testText.getDelta().toJSON(), [ + t.compare(testText.getContent().toJSON(), [ { insert: 'Test', attributes: { bold: true } }, { insert: '\n' }, { insert: 'Multi-line', attributes: { bold: true } }, @@ -1753,7 +1756,7 @@ export const testNotMergeEmptyLinesFormat = _tc => { { insert: '\nText' }, { insert: '\n', attributes: { title: true } } ]) - t.compare(testText.getDelta().toJSON(), [ + t.compare(testText.getContent().toJSON(), [ { insert: 'Text' }, { insert: '\n', attributes: { title: true } }, { insert: '\nText' }, @@ -1777,7 +1780,7 @@ export const testPreserveAttributesThroughDelete = _tc => { { delete: 1 }, { retain: 1, attributes: { title: true } } ]) - t.compare(testText.getDelta().toJSON(), [ + t.compare(testText.getContent().toJSON(), [ { insert: 'Text' }, { insert: '\n', attributes: { title: true } } ]) @@ -1791,7 +1794,7 @@ export const testGetDeltaWithEmbeds = tc => { text0.applyDelta([{ insert: { linebreak: 's' } }]) - t.compare(text0.getDelta().toJSON(), [{ + t.compare(text0.getContent().toJSON(), [{ insert: { linebreak: 's' } }]) } @@ -1804,7 +1807,7 @@ export const testTypesAsEmbed = tc => { text0.applyDelta([{ insert: new Y.Map([['key', 'val']]) }]) - t.compare(/** @type {delta.InsertEmbedOp} */ (text0.getDelta().ops[0]).insert.toJSON(), { key: 'val' }) + t.compare(/** @type {delta.InsertEmbedOp} */ (text0.getContent().ops[0]).insert.toJSON(), { key: 'val' }) let firedEvent = false text1.observe(event => { const d = event.delta @@ -1813,7 +1816,7 @@ export const testTypesAsEmbed = tc => { firedEvent = true }) testConnector.flushAllMessages() - const delta = text1.getDelta().toJSON() + const delta = text1.getContent().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 +1850,11 @@ export const testSnapshot = tc => { }, { delete: 1 }]) - const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1)) + const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) t.compare(state1.toJSON(), [{ insert: 'abcd' }]) - const state2 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot2)) + const state2 = text0.getContent(createAttributionManagerFromSnapshots(snapshot2)) t.compare(state2.toJSON(), [{ insert: 'axcd' }]) - const state2Diff = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON() + const state2Diff = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON() const expected = [{ insert: 'a' }, { insert: 'x', attribution: { insert: [] } }, { insert: 'b', attribution: { delete: [] } }, { insert: 'cd' }] t.compare(state2Diff, expected) } @@ -1872,8 +1875,8 @@ export const testSnapshotDeleteAfter = tc => { }, { insert: 'e' }]) - const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1)) - t.compare(state1, delta.fromJSON([{ insert: 'abcd' }])) + const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) + t.compare(state1, delta.createTextDelta().insert('abcd')) } /** @@ -1892,7 +1895,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.getDelta().toJSON() + const delta0 = text0.getContent().toJSON() t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }]) } @@ -1903,7 +1906,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.getDelta().toJSON() + const delta0 = text0.getContent().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') } @@ -1944,7 +1947,7 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => { }) testConnector.flushAllMessages() /** - * @type {Array>} + * @type {Array>} */ const deltas = [] text0.observe(event => { @@ -2211,9 +2214,9 @@ export const testFormattingBug = async _tc => { { insert: '\n', attributes: { url: 'http://docs.yjs.dev' } }, { insert: '\n', attributes: { url: 'http://example.com' } } ] - t.compare(text1.getDelta().toJSON(), expectedResult) - t.compare(text1.getDelta().toJSON(), text2.getDelta().toJSON()) - console.log(text1.getDelta().toJSON()) + t.compare(text1.getContent().toJSON(), expectedResult) + t.compare(text1.getContent().toJSON(), text2.getContent().toJSON()) + console.log(text1.getContent().toJSON()) } /** @@ -2241,8 +2244,8 @@ export const testDeleteFormatting = _tc => { { insert: 'on ', attributes: { bold: true } }, { insert: 'fire off the shoulder of Orion.' } ] - t.compare(text.getDelta().toJSON(), expected) - t.compare(text2.getDelta().toJSON(), expected) + t.compare(text.getContent().toJSON(), expected) + t.compare(text2.getContent().toJSON(), expected) } /** @@ -2261,14 +2264,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.getDelta(attributionManager) + const attributedContent = ytext.getContent(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.getDelta(attributionManager) + const attributedContent = ytext.getContent(attributionManager) console.log(attributedContent.toJSON()) t.assert(attributedContent.equals(expectedContent)) }) @@ -2299,7 +2302,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.getDelta(attributionManager) + const attributedContent = ytext.getContent(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 +2586,7 @@ export const testAttributionManagerDefaultPerformance = tc => { }) t.measureTime(`getContent(attributionManager) performance `, () => { for (let i = 0; i < M; i++) { - ytext.getDelta() + ytext.getContent() } }) } diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index 9d504cd1..9015f9c4 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.getDelta().toJSON(), delta) + t.compare(yxml.getContent().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.getDelta(attributionManager) + const attributedContent = yfragment.getContent(attributionManager) console.log(attributedContent.children.toJSON()) t.assert(attributedContent.children.equals(expectedContent)) - t.compare(elem1.getDelta(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) + t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) }) } @@ -273,17 +273,17 @@ 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.getDelta(attributionManager) + const attributedContent = yelement.getContent(attributionManager) console.log('children', attributedContent.children.toJSON()) console.log('attributes', attributedContent.attributes) t.assert(attributedContent.children.equals(expectedContent)) - t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } }) + t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', 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.createXmlDelta('span')]) .insert([ delta.createTextDelta().insert('world', null, { insert: [] }) ], null, { insert: [] }) @@ -292,7 +292,8 @@ export const testElementAttributedContent = _tc => { console.log('cs expec', JSON.stringify(expectedContent.toJSON(), 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.compare(attributedContent.attributes, /** @type {delta.MapDeltaBuilder} */ (delta.createMapDelta()).set('key', '42', undefined, { insert: [] })) + t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } }) t.assert(attributedContent.nodeName === 'UNDEFINED') }) }) @@ -321,13 +322,13 @@ export const testElementAttributedContentViaDiffer = _tc => { console.log('attributes', attributedContent.attributes) t.compare(attributedContent.children.toJSON(), expectedContent.toJSON()) t.assert(attributedContent.children.equals(expectedContent)) - t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } }) + t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } }) t.group('test getContentDeep', () => { const expectedContent = delta.createArrayDelta().insert( [delta.createTextDelta().insert('hello')], null, { delete: [] } - ).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }]) + ).insert([delta.createXmlDelta('span')]) .insert([ delta.createTextDelta().insert('world', null, { insert: [] }) ], null, { insert: [] }) @@ -336,7 +337,7 @@ export const testElementAttributedContentViaDiffer = _tc => { console.log('cs expec', JSON.stringify(expectedContent.toJSON(), 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.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } }) t.assert(attributedContent.nodeName === 'UNDEFINED') }) ydoc.transact(() => { @@ -348,7 +349,7 @@ export const testElementAttributedContentViaDiffer = _tc => { [delta.createTextDelta().insert('hello')], null, { delete: [] } - ).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }]) + ).insert([delta.createXmlDelta('span')]) .insert([ delta.createTextDelta().insert('bigworld', null, { insert: [] }) ], null, { insert: [] }) @@ -357,13 +358,13 @@ export const testElementAttributedContentViaDiffer = _tc => { console.log('cs expec', JSON.stringify(expectedContent.toJSON(), 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.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } }) t.assert(attributedContent.nodeName === 'UNDEFINED') }) Y.applyUpdate(ydocV1, Y.encodeStateAsUpdate(ydoc)) t.group('test getContentDeep both docs synced', () => { t.info('expecting diffingAttributionManager to auto update itself') - const expectedContent = delta.createArrayDelta().insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }]).insert([ + const expectedContent = delta.createArrayDelta().insert([delta.createXmlDelta('span')]).insert([ delta.createTextDelta().insert('bigworld') ]) const attributedContent = yelement.getContentDeep(attributionManager) @@ -371,7 +372,7 @@ export const testElementAttributedContentViaDiffer = _tc => { console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2)) console.log('attributes', attributedContent.attributes) t.assert(attributedContent.children.equals(expectedContent)) - t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: null } }) + t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: null } }) t.assert(attributedContent.nodeName === 'UNDEFINED') }) }