From 301d3836ab6f048d197d83a02ed99602f0d48811 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 24 Dec 2025 09:18:38 +0100 Subject: [PATCH] simplifying type system --- package-lock.json | 55 ++++---- package.json | 2 +- src/types/AbstractType.js | 69 +++++----- src/types/Type.js | 270 +++++++++++++++++++++++++++++++++++++ src/utils/YEvent.js | 30 ++--- src/utils/delta-helpers.js | 2 +- tests/attribution.tests.js | 28 ++-- tests/delta.tests.js | 6 +- tests/undo-redo.tests.js | 4 +- 9 files changed, 360 insertions(+), 106 deletions(-) create mode 100644 src/types/Type.js diff --git a/package-lock.json b/package-lock.json index 57d88005..6cdf6503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "14.0.0-19", "license": "MIT", "dependencies": { - "lib0": "^0.2.115" + "lib0": "file:../lib0" }, "devDependencies": { "@types/node": "^22.14.1", @@ -29,6 +29,28 @@ "url": "https://github.com/sponsors/dmonad" } }, + "../lib0": { + "version": "0.2.115", + "license": "MIT", + "bin": { + "0ecdsa-generate-keypair": "src/bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "src/bin/gentesthtml.js", + "0serve": "src/bin/0serve.js" + }, + "devDependencies": { + "@types/node": "^24.0.14", + "c8": "^10.1.3", + "standard": "^17.1.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -3143,16 +3165,6 @@ "dev": true, "license": "ISC" }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "license": "MIT", - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -3333,25 +3345,8 @@ } }, "node_modules/lib0": { - "version": "0.2.115", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115.tgz", - "integrity": "sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g==", - "license": "MIT", - "dependencies": { - "isomorphic.js": "^0.2.4" - }, - "bin": { - "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", - "0gentesthtml": "bin/gentesthtml.js", - "0serve": "bin/0serve.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } + "resolved": "../lib0", + "link": true }, "node_modules/linkify-it": { "version": "5.0.0", diff --git a/package.json b/package.json index 2ee3199e..4b5bbcf6 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ }, "homepage": "https://docs.yjs.dev", "dependencies": { - "lib0": "^0.2.115" + "lib0": "file:../lib0" }, "devDependencies": { "@types/node": "^22.14.1", diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index 0bde7e0f..15791ae5 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -275,8 +275,7 @@ export const callTypeObservers = (type, transaction, event) => { /** * Abstract Yjs Type class - * @template {delta.Delta} [EventDelta=any] - * @template {AbstractType} [Self=any] + * @template {delta.DeltaConf} [DConf=any] */ export class AbstractType { constructor () { @@ -299,7 +298,7 @@ export class AbstractType { this._length = 0 /** * Event handlers - * @type {EventHandler,Transaction>} + * @type {EventHandler,Transaction>} */ this._eH = createEventHandler() /** @@ -312,14 +311,15 @@ export class AbstractType { */ this._searchMarker = null /** - * @type {EventDelta?} + * @type {delta.DeltaBuilder} + * @private */ - this._prelim = null + this._content = /** @type {delta.DeltaBuilderAny} */ (delta.create()) } /** * Returns a fresh delta that can be used to change this YType. - * @type {EventDelta} + * @type {delta.DeltaBuilder} */ get change () { return /** @type {any} */ (delta.create()) @@ -352,7 +352,7 @@ export class AbstractType { } /** - * @return {Self} + * @return {this} */ _copy () { // @ts-ignore @@ -364,7 +364,7 @@ export class AbstractType { * * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. * - * @return {Self} + * @return {this} */ clone () { // @todo remove this method from othern types by doing `_copy().apply(this.getContent())` @@ -405,7 +405,7 @@ export class AbstractType { /** * Observe all events that are created on this type. * - * @template {(target: YEvent, tr: Transaction) => void} F + * @template {(target: YEvent, tr: Transaction) => void} F * @param {F} f Observer function * @return {F} */ @@ -429,7 +429,7 @@ export class AbstractType { /** * Unregister an observer function. * - * @param {(type:YEvent,tr:Transaction)=>void} f Observer function + * @param {(type:YEvent,tr:Transaction)=>void} f Observer function */ unobserve (f) { removeEventHandlerListener(this._eH, f) @@ -467,16 +467,16 @@ export class AbstractType { * @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes * @param {Map>|null} [opts.modified] - set of types that should be rendered as modified children * @param {Deep} [opts.deep] - render child types as delta - * @return {Deep extends true ? ToDeepEventDelta : EventDelta} The Delta representation of this type. + * @return {Deep extends true ? delta.Delta> : delta.Delta} The Delta representation of this type. * * @public */ getContent (am = noAttributionsManager, opts = {}) { const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, modified = null, deep = false } = opts const renderAttrs = modified?.get(this) || null - const renderChildren = (modified == null || opts.modified.get(this)?.has(null)) + const renderChildren = !!(modified == null || modified.get(this)?.has(null)) /** - * @type {EventDelta extends delta.Delta ? delta.DeltaBuilder : never} + * @type {delta.DeltaBuilderAny} */ const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null)) const optsAll = modified == null ? opts : object.assign({}, opts, { modified: null }) @@ -693,7 +693,7 @@ export class AbstractType { * attributions. * * @param {AbstractAttributionManager} am - * @return {ToDeepEventDelta} + * @return {delta.Delta>} */ getContentDeep (am = noAttributionsManager) { return /** @type {any} */ (this.getContent(am, { deep: true })) @@ -702,7 +702,7 @@ export class AbstractType { /** * Apply a {@link Delta} on this shared type. * - * @param {delta.Delta} d The changes to apply on this element. + * @param {delta.DeltaAny} d The changes to apply on this element. * @param {AbstractAttributionManager} am * * @public @@ -747,12 +747,12 @@ export class AbstractType { } } for (const op of d.attrs) { - if (delta.$insertOp.check(op)) { - typeMapSet(transaction, /** @type {any} */ (this), op.key, op.value) + if (delta.$setAttrOp.check(op)) { + typeMapSet(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key), op.value) } else if (delta.$deleteOp.check(op)) { - typeMapDelete(transaction, /** @type {any} */ (this), op.key) + typeMapDelete(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key)) } else { - const sub = typeMapGet(/** @type {any} */ (this), op.key) + const sub = typeMapGet(/** @type {any} */ (this), /** @type {any} */ (op.key)) if (!(sub instanceof AbstractType)) { error.unexpectedCase() } @@ -772,21 +772,14 @@ export class AbstractType { export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b)) /** - * @template {delta.Delta} D - * @typedef {D extends delta.Delta - * ? delta.Delta< - * N, - * { [K in keyof Attrs]: TypeToDelta }, - * TypeToDelta, - * Text - * > - * : D - * } ToDeepEventDelta - */ - -/** - * @template {any} T - * @typedef {(Extract> extends AbstractType ? (unknown extends D ? never : ToDeepEventDelta) : never) | Exclude>} TypeToDelta + * @template {delta.DeltaConf} DConf + * @typedef {delta.DeltaConfOverwrite]: delta.DeltaConfGetAttrs[K] }, + * children: (Extract,AbstractType> extends AbstractType ? ( + * unknown extends SubDConf ? never : (delta.Delta>) + * ) : never) | Exclude,AbstractType> + * }> + * } DeltaConfTypesToDelta */ /** @@ -1309,7 +1302,7 @@ export const typeMapGetAll = (parent) => { * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the * attribution `{ isDeleted: true, .. }`. * - * @template {delta.DeltaBuilder} TypeDelta + * @template {delta.DeltaBuilderAny} TypeDelta * @param {TypeDelta} d * @param {YType_} parent * @param {Set?} attrsToRender @@ -1341,10 +1334,10 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de let c = array.last(content.getContent()) if (deleted) { if (itemsToRender == null || itemsToRender.hasId(item.lastId)) { - d.unset(key, attribution, c) + d.deleteAttr(key, attribution, c) } } else if (deep && c instanceof AbstractType && modified?.has(c)) { - d.update(key, c.getContent(am, opts)) + d.modifyAttr(key, c.getContent(am, opts)) } else { // find prev content let prevContentItem = item @@ -1356,7 +1349,7 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de if (deep && c instanceof AbstractType) { c = /** @type {any} */(c).getContent(am, optsAll) } - d.set(key, c, attribution, prevValue) + d.setAttr(key, c, attribution, prevValue) } } if (attrsToRender == null) { diff --git a/src/types/Type.js b/src/types/Type.js new file mode 100644 index 00000000..320a8bb7 --- /dev/null +++ b/src/types/Type.js @@ -0,0 +1,270 @@ +/** + * @module YArray + */ + +import { + AbstractType, + typeListGet, + typeListToArray, + typeListForEach, + typeListCreateIterator, + typeListInsertGenerics, + typeListPushGenerics, + typeListDelete, + typeListMap, + YArrayRefID, + transact, + warnPrematureAccess, + typeListSlice, + noAttributionsManager, + AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line +} from '../internals.js' + +import * as delta from 'lib0/delta' + +/** + * A shared Array implementation. + * @template {import('../utils/types.js').YValue} T + * @extends {AbstractType,YArray>} + * @implements {Iterable} + */ +// @todo remove this +// @ts-ignore +export class YType extends AbstractType { + constructor () { + super() + /** + * @type {Array?} + * @private + */ + this._prelimContent = [] + /** + * @type {Array} + */ + this._searchMarker = [] + } + + /** + * Construct a new YArray containing the specified items. + * @template {import('../utils/types.js').YValue} T + * @param {Array} items + * @return {YArray} + */ + static from (items) { + /** + * @type {YArray} + */ + const a = new YArray() + a.push(items) + return a + } + + /** + * Integrate this type into the Yjs instance. + * + * * Save this struct in the os + * * This type is sent to other client + * * Observer functions are fired + * + * @param {Doc} y The Yjs instance + * @param {Item?} item + */ + _integrate (y, item) { + super._integrate(y, item) + this.insert(0, /** @type {Array} */ (this._prelimContent)) + this._prelimContent = null + } + + /** + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {YArray} + */ + clone () { + /** + * @type {this} + */ + const arr = /** @type {this} */ (new YArray()) + arr.insert(0, this.toArray().map(el => + // @ts-ignore + el instanceof AbstractType ? /** @type {any} */ (el.clone()) : el + )) + return arr + } + + get length () { + this.doc ?? warnPrematureAccess() + return this._length + } + + /** + * Inserts new content at an index. + * + * Important: This function expects an array of content. Not just a content + * object. The reason for this "weirdness" is that inserting several elements + * is very efficient when it is done as a single operation. + * + * @example + * // Insert character 'a' at position 0 + * yarray.insert(0, ['a']) + * // Insert numbers 1, 2 at position 1 + * yarray.insert(1, [1, 2]) + * + * @param {number} index The index to insert content at. + * @param {Array} content The array of content + */ + insert (index, content) { + if (this.doc !== null) { + transact(this.doc, transaction => { + typeListInsertGenerics(transaction, this, index, /** @type {any} */ (content)) + }) + } else { + /** @type {Array} */ (this._prelimContent).splice(index, 0, ...content) + } + } + + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + * + * @todo Use the following implementation in all types. + */ + push (content) { + if (this.doc !== null) { + transact(this.doc, transaction => { + typeListPushGenerics(transaction, this, /** @type {any} */ (content)) + }) + } else { + /** @type {Array} */ (this._prelimContent).push(...content) + } + } + + /** + * Prepends content to this YArray. + * + * @param {Array} content Array of content to prepend. + */ + unshift (content) { + this.insert(0, content) + } + + /** + * Deletes elements starting from an index. + * + * @param {number} index Index at which to start deleting elements + * @param {number} length The number of elements to remove. Defaults to 1. + */ + delete (index, length = 1) { + if (this.doc !== null) { + transact(this.doc, transaction => { + typeListDelete(transaction, this, index, length) + }) + } else { + /** @type {Array} */ (this._prelimContent).splice(index, length) + } + } + + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {T} + */ + get (index) { + return typeListGet(this, index) + } + + /** + * Transforms this YArray to a JavaScript Array. + * + * @return {Array} + */ + toArray () { + return typeListToArray(this) + } + + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @param {AbstractAttributionManager} am + * @return {delta.ArrayDelta>} The Delta representation of this type. + * + * @public + */ + getContentDeep (am = noAttributionsManager) { + return super.getContentDeep(am) + } + + /** + * Returns a portion of this YArray into a JavaScript Array selected + * from start to end (end not included). + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice (start = 0, end = this.length) { + return typeListSlice(this, start, end) + } + + /** + * Transforms this Shared Type to a JSON object. + * + * @return {Array} + */ + toJSON () { + return this.map(c => c instanceof AbstractType ? c.toJSON() : c) + } + + /** + * Returns an Array with the result of calling a provided function on every + * element of this YArray. + * + * @template M + * @param {function(T,number,YArray):M} f Function that produces an element of the new Array + * @return {Array} A new array with each element being the result of the + * callback function + */ + map (f) { + return typeListMap(this, /** @type {any} */ (f)) + } + + /** + * Executes a provided function once on every element of this YArray. + * + * @param {function(T,number,YArray):void} f A function to execute on every element of this YArray. + */ + forEach (f) { + typeListForEach(this, f) + } + + /** + * @return {IterableIterator} + */ + [Symbol.iterator] () { + return typeListCreateIterator(this) + } + + /** + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder + */ + _write (encoder) { + encoder.writeTypeRef(YArrayRefID) + } +} + +/** + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} + * + * @private + * @function + */ +export const readYArray = _decoder => new YArray() diff --git a/src/utils/YEvent.js b/src/utils/YEvent.js index 42930471..68575d5d 100644 --- a/src/utils/YEvent.js +++ b/src/utils/YEvent.js @@ -13,28 +13,24 @@ import * as delta from 'lib0/delta' // eslint-disable-line import * as set from 'lib0/set' /** - * @typedef {import('./types.js').YType} _YType - */ - -/** - * @template {AbstractType} Target + * @template {delta.DeltaConf} DConf * YEvent describes the changes on a YType. */ export class YEvent { /** - * @param {Target} target The changed type. + * @param {AbstractType} target The changed type. * @param {Transaction} transaction * @param {Set?} subs The keys that changed */ constructor (target, transaction, subs) { /** * The type on which this event was created on. - * @type {Target} + * @type {AbstractType} */ this.target = target /** * The current target on which the observe callback is called. - * @type {_YType} + * @type {AbstractType} */ this.currentTarget = target /** @@ -43,11 +39,11 @@ export class YEvent { */ this.transaction = transaction /** - * @type {(Target extends AbstractType ? D : delta.Delta)|null} + * @type {delta.Delta|null} */ this._delta = null /** - * @type {(Target extends AbstractType ? import('../internals.js').ToDeepEventDelta : delta.Delta)|null} + * @type {import('../types/AbstractType.js').DeltaConfTypesToDeltaDelta|null} */ this._deltaDeep = null /** @@ -120,7 +116,7 @@ export class YEvent { * @param {AbstractAttributionManager} am * @param {object} [opts] * @param {Deep} [opts.deep] - * @return {Target extends AbstractType ? (Deep extends true ? import('../internals.js').ToDeepEventDelta : D) : delta.Delta} The Delta representation of this type. + * @return {Deep extends true ? delta.Delta> : delta.Delta} The Delta representation of this type. * * @public */ @@ -159,7 +155,7 @@ export class YEvent { * Compute the changes in the delta format. * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. * - * @type {Target extends AbstractType ? D : delta.Delta} The Delta representation of this type. + * @type {delta.Delta} The Delta representation of this type. * @public */ get delta () { @@ -170,11 +166,11 @@ export class YEvent { * Compute the changes in the delta format. * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. * - * @type {Target extends AbstractType ? D : delta.Delta} The Delta representation of this type. + * @type {import('../internals.js').DeltaConfTypesToDeltaDelta} The Delta representation of this type. * @public */ get deltaDeep () { - return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = this.getDelta(noAttributionsManager, { deep: true }))) + return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = /** @type {any} */ (this.getDelta(noAttributionsManager, { deep: true })))) } } @@ -188,8 +184,8 @@ export class YEvent { * console.log(path) // might look like => [2, 'key1'] * child === type.get(path[0]).get(path[1]) * - * @param {_YType} parent - * @param {_YType} child target + * @param {AbstractType} parent + * @param {AbstractType} child target * @param {AbstractAttributionManager} am * @return {Array} Path to the target * @@ -209,7 +205,7 @@ export const getPathTo = (parent, child, am = noAttributionsManager) => { const apos = /** @type {AbsolutePosition} */ (createAbsolutePositionFromRelativePosition(createRelativePosition(parent, child._item.id), doc, false, am)) path.unshift(apos.index) } - child = /** @type {_YType} */ (child._item.parent) + child = /** @type {AbstractType} */ (child._item.parent) } return path } diff --git a/src/utils/delta-helpers.js b/src/utils/delta-helpers.js index 5c4c5c66..9511d79b 100644 --- a/src/utils/delta-helpers.js +++ b/src/utils/delta-helpers.js @@ -46,7 +46,7 @@ export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff( const shareDelta = type.getContent(am, { itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true }) - d.update(typename, shareDelta) + d.modifyAttr(typename, shareDelta) } }) }) diff --git a/tests/attribution.tests.js b/tests/attribution.tests.js index 02f43348..96ea0c5c 100644 --- a/tests/attribution.tests.js +++ b/tests/attribution.tests.js @@ -40,11 +40,11 @@ export const testAttributedEvents = _tc => { }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) const c1 = ytext.getContent(am) - t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] })) + t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] })) let calledObserver = false ytext.observe(event => { const d = event.getDelta(am) - t.compare(d, delta.text().retain(11).insert('!', null, { insert: [] })) + t.compare(d, delta.create().retain(11).insert('!', null, { insert: [] })) calledObserver = true }) ytext.insert(11, '!') @@ -64,8 +64,8 @@ export const testInsertionsMindingAttributedContent = _tc => { }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) const c1 = ytext.getContent(am) - t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] })) - ytext.applyDelta(delta.text().retain(11).insert('content'), am) + t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] })) + ytext.applyDelta(delta.create().retain(11).insert('content'), am) t.assert(ytext.toString() === 'hello content') } @@ -82,8 +82,8 @@ export const testInsertionsIntoAttributedContent = _tc => { }) const am = Y.createAttributionManagerFromDiff(v1, ydoc) const c1 = ytext.getContent(am) - t.compare(c1, delta.text().insert('hello ').insert('word', null, { insert: [] })) - ytext.applyDelta(delta.text().retain(9).insert('l'), am) + t.compare(c1, delta.create().insert('hello ').insert('word', null, { insert: [] })) + ytext.applyDelta(delta.create().retain(9).insert('l'), am) t.assert(ytext.toString() === 'hello world') } @@ -102,9 +102,9 @@ export const testYdocDiff = () => { const d = Y.diffDocsToDelta(ydocStart, ydocUpdated) console.log('calculated diff', d.toJSON()) t.compare(d, delta.create() - .update('text', delta.create().retain(5).insert(' world', null, { insert: [] })) - .update('array', delta.create().retain(1).insert(['x'], null, { insert: [] })) - .update('map', delta.create().set('newk', 42, { insert: [] }).update('nested', delta.create().insert([1], null, { insert: [] }))) + .modifyAttr('text', delta.create().retain(5).insert(' world', null, { insert: [] })) + .modifyAttr('array', delta.create().retain(1).insert(['x'], null, { insert: [] })) + .modifyAttr('map', delta.create().setAttr('newk', 42, { insert: [] }).modifyAttr('nested', delta.create().insert([1], null, { insert: [] }))) ) } @@ -113,21 +113,21 @@ export const testChildListContent = () => { const ydocUpdated = Y.cloneDoc(ydocStart) const yf = new Y.XmlElement('test') let calledEvent = 0 - yf.applyDelta(delta.create().insert('test content').set('k', 'v')) + yf.applyDelta(delta.create().insert('test content').setAttr('k', 'v')) const yarray = ydocUpdated.getArray('array') yarray.observeDeep((events, tr) => { calledEvent++ const event = events.find(event => event.target === yarray) || new Y.YEvent(yarray, tr, new Set(null)) const d = event.deltaDeep - const expectedD = delta.create().insert([delta.create('test').insert('test content').set('k', 'v')]) + const expectedD = delta.create().insert([delta.create('test').insert('test content').setAttr('k', 'v')]) t.compare(d, expectedD) }) ydocUpdated.getArray('array').insert(0, [yf]) t.assert(calledEvent === 1) const d = Y.diffDocsToDelta(ydocStart, ydocUpdated) console.log('calculated diff', d.toJSON()) - t.compare(d, delta.create() - .update('array', delta.create().insert([delta.create('test').insert('test content', null, { insert: [] }).set('k', 'v', { insert: [] })], null, { insert: [] })) - ) + const expected = delta.create() + .modifyAttr('array', delta.create().insert([delta.create('test').insert('test content', null, { insert: [] }).setAttr('k', 'v', { insert: [] })], null, { insert: [] })) + t.compare(d.done(), expected.done()) } diff --git a/tests/delta.tests.js b/tests/delta.tests.js index 0c6319eb..e1181203 100644 --- a/tests/delta.tests.js +++ b/tests/delta.tests.js @@ -84,7 +84,7 @@ export const testDeltaBasicSchema = _tc => { * @param {t.TestCase} _tc */ export const testDeltaValues = _tc => { - const change = delta.create().set('a', 42).unset('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }]) + const change = delta.create().setAttr('a', 42).deleteAttr('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }]) // iterate through attribute changes for (const attrChange of change.attrs) { if (delta.$insertOp.check(attrChange)) { @@ -122,7 +122,7 @@ export const testBasics = _tc => { const ydoc = new Y.Doc() const ytype = ydoc.get('my data') /** - * @type {delta.Delta} + * @type {delta.Delta<{attrs: { a: number }, children: { my: string }, text: true }>} */ let observedDelta = delta.create() ytype.observe(event => { @@ -130,7 +130,7 @@ export const testBasics = _tc => { console.log('ytype changed:', observedDelta.toJSON()) }) // define a change: set attribute: a=42 - const attrChange = delta.create().set('a', 42).done() + const attrChange = delta.create().setAttr('a', 42).done() // define a change: insert textual content and an object const childChange = delta.create().insert('hello').insert([{ my: 'object' }]).done() // merge changes diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index c40c6985..a1e3acf7 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -299,8 +299,8 @@ export const testUndoXml = tc => { // format textchild and revert that change undoManager.stopCapturing() textchild.format(3, 4, { bold: true }) - const v1 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('con').insert('tent', { bold: true }).done()]).done()]).done() - const v2 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('content').done()]).done()]).done() + const v1 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.create().insert('con').insert('tent', { bold: true }).done()]).done()]).done() + const v2 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.create().insert('content').done()]).done()]).done() t.compare(xml0.getContentDeep(), v1) undoManager.undo() t.compare(xml0.getContentDeep(), v2)