From 1433cfeebf378127f2276f20d3998272e17210e7 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Tue, 29 Apr 2025 18:02:15 +0200 Subject: [PATCH] Implement & test getContentDeep for all types. Improve ability to compare things using lib0/traits. --- package-lock.json | 8 +- package.json | 2 +- src/types/AbstractType.js | 33 +++++-- src/types/YArray.js | 33 +++++-- src/types/YMap.js | 6 +- src/types/YText.js | 24 ++++- src/types/YXmlElement.js | 34 ++++++- src/types/YXmlFragment.js | 17 +++- src/utils/Delta.js | 184 ++++++++++++++++++++++++++------------ src/utils/IdSet.js | 1 - test.html | 6 ++ tests/IdMap.tests.js | 2 +- tests/delta.tests.js | 4 +- tests/doc.tests.js | 4 +- tests/y-array.tests.js | 4 +- tests/y-map.tests.js | 13 ++- tests/y-text.tests.js | 24 ++--- tests/y-xml.tests.js | 30 +++++-- 18 files changed, 311 insertions(+), 118 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c34e573..c1987cc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "13.6.24", "license": "MIT", "dependencies": { - "lib0": "^0.2.104", + "lib0": "^0.2.105", "y-protocols": "^1.0.5" }, "devDependencies": { @@ -2774,9 +2774,9 @@ } }, "node_modules/lib0": { - "version": "0.2.104", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.104.tgz", - "integrity": "sha512-1tqKRANSPTcjs/yjPoKh52oRM2u5AYdd8jie8sDiN8/5kpWWiQSHUGgtB4VEXLw1chVL3QPSPp8q9RWqzSn2FA==", + "version": "0.2.105", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.105.tgz", + "integrity": "sha512-5vtbuBi2P43ZYOfVMV+TZYkWEa0J9kijXirzEgrPA+nJDQCtMx805/rqW4G1nXbM9IRIhwW+OyNNgcQdbhKfSw==", "license": "MIT", "dependencies": { "isomorphic.js": "^0.2.4" diff --git a/package.json b/package.json index b0a06dc5..5479ac37 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ }, "homepage": "https://docs.yjs.dev", "dependencies": { - "lib0": "^0.2.104", + "lib0": "^0.2.105", "y-protocols": "^1.0.5" }, "devDependencies": { diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index b1ac51a5..228465b8 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -10,8 +10,7 @@ import { ContentAny, ContentBinary, getItemCleanStart, - ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, - createAttributionFromAttrs, // eslint-disable-line + ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttrs, AbstractAttributionManager, // eslint-disable-line } from '../internals.js' import * as delta from '../utils/Delta.js' @@ -22,6 +21,13 @@ import * as error from 'lib0/error' import * as math from 'lib0/math' import * as 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 */ @@ -406,6 +412,22 @@ export class AbstractType { * @return {any} */ toJSON () {} + + /** + * @param {AbstractAttributionManager} _am + * @return {any} + */ + getContent (_am) { + error.methodUnimplemented() + } + + /** + * @param {AbstractAttributionManager} _am + * @return {DeepContent} + */ + getContentDeep (_am) { + error.methodUnimplemented() + } } /** @@ -476,17 +498,15 @@ export const typeListToArray = type => { * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the * attribution `{ isDeleted: true, .. }`. * - * @template MapType * @param {AbstractType} type * @param {import('../internals.js').AbstractAttributionManager} am - * @return {delta.Delta>} The Delta representation of this type. * * @private * @function */ export const typeListGetContent = (type, am) => { type.doc ?? warnPrematureAccess() - const d = /** @type {delta.DeltaBuilder>} */ (delta.create()) + const d = delta.createArrayDelta() /** * @type {Array>} */ @@ -502,7 +522,7 @@ export const typeListGetContent = (type, am) => { d.insert(content.getContent(), null, attribution) } } - return d.done() + return d } /** @@ -1014,7 +1034,6 @@ export const typeMapGetContent = (parent, am) => { return mapcontent } - /** * @param {AbstractType} parent * @param {string} key diff --git a/src/types/YArray.js b/src/types/YArray.js index 0fd94e37..086ac001 100644 --- a/src/types/YArray.js +++ b/src/types/YArray.js @@ -17,10 +17,13 @@ import { callTypeObservers, transact, warnPrematureAccess, - ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line - AbstractAttributionManager + typeListGetContent, + typeListSlice, + noAttributionsManager, + AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' -import { typeListGetContent, typeListSlice } from './AbstractType.js' + +import * as delta from '../utils/Delta.js' /** * Event that describes the changes on a YArray @@ -216,11 +219,31 @@ export class YArray extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').Delta>} The Delta representation of this type. + * @return {import('../utils/Delta.js').ArrayDelta>} The Delta representation of this type. * * @public */ - getContent (am) { + getContentDeep (am = noAttributionsManager) { + return this.getContent(am).map(d => /** @type {any} */ ( + d instanceof delta.InsertOp && d.insert instanceof Array + ? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution) + : d + )) + } + + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @param {AbstractAttributionManager} am + * @return {import('../utils/Delta.js').ArrayDelta>} The Delta representation of this type. + * + * @public + */ + getContent (am = noAttributionsManager) { return typeListGetContent(this, am) } diff --git a/src/types/YMap.js b/src/types/YMap.js index 78f90e63..7c0e0cbe 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -13,13 +13,11 @@ import { YMapRefID, callTypeObservers, transact, + typeMapGetContent, warnPrematureAccess, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line - createAttributionFromAttrs, - typeMapGetContent + UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' -import * as array from 'lib0/array' import * as iterator from 'lib0/iterator' /** diff --git a/src/types/YText.js b/src/types/YText.js index e00d6cb4..fa32cc80 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -1000,7 +1000,27 @@ export class YText extends AbstractType { * attribution `{ isDeleted: true, .. }`. * * @param {AbstractAttributionManager} am - * @return {import('../utils/Delta.js').Delta} The Delta representation of this type. + * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. + * + * @public + */ + getContentDeep (am = noAttributionsManager) { + return this.getContent(am).map(d => + d instanceof delta.InsertOp && d.insert instanceof AbstractType + ? new delta.InsertOp(d.insert.getContent(am), d.attributes, d.attribution) + : d + ) + } + + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @param {AbstractAttributionManager} am + * @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type. * * @public */ @@ -1068,7 +1088,7 @@ export class YText extends AbstractType { } } } - return d.done() + return d } /** diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index b3a228aa..aca5fbb0 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -13,9 +13,11 @@ import { YXmlElementRefID, typeMapGetContent, noAttributionsManager, - Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line + AbstractAttributionManager, Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line } from '../internals.js' +import * as delta from '../utils/Delta.js' + /** * @typedef {Object|number|null|Array|string|Uint8Array|AbstractType} ValueTypes */ @@ -208,6 +210,36 @@ export class YXmlElement extends YXmlFragment { return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this)) } + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @param {AbstractAttributionManager} am + * @return {{ nodeName: string, children: delta.ArrayDelta>, attributes: import('./AbstractType.js').MapAttributedContent }} + * + * @public + */ + getContentDeep (am = noAttributionsManager) { + const { children: origChildren, attributes: origAttributes } = this.getContent(am) + const children = origChildren.map(d => /** @type {any} */ ( + (d instanceof delta.InsertOp && d.insert instanceof Array) + ? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution) + : d + )) + /** + * @todo there is a Attributes type and a DeepAttributes type. + * @type {import('./AbstractType.js').MapAttributedContent} + */ + const attributes = {} + object.forEach(origAttributes, (v, key) => { + attributes[key] = Object.assign({}, v, { value: v.value instanceof AbstractType ? v.value.getContentDeep(am) : v.value }) + }) + return { nodeName: this.nodeName, children, attributes } + } + /** * Render the difference to another ydoc (which can be empty) and highlight the differences with * attributions. diff --git a/src/types/YXmlFragment.js b/src/types/YXmlFragment.js index 72adf50c..c60f11ce 100644 --- a/src/types/YXmlFragment.js +++ b/src/types/YXmlFragment.js @@ -383,13 +383,28 @@ 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').Delta> }} + * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} */ getContent (am = noAttributionsManager) { const children = typeListGetContent(this, am) return { children } } + /** + * Calculate the attributed content using the attribution manager. + * + * @param {import('../internals.js').AbstractAttributionManager} am + * @return {{ children: import('../utils/Delta.js').ArrayDelta> }} + */ + getContentDeep (am) { + const { children: origChildren } = this.getContent() + /** + * @type {import('../utils/Delta.js').ArrayDelta>} + */ + const children = origChildren.map(d => /** @type {any} */ (d instanceof AbstractType ? d.getContentDeep(am) : d)) + return { children } + } + /** * Appends content to this YArray. * diff --git a/src/utils/Delta.js b/src/utils/Delta.js index 794b1251..22102614 100644 --- a/src/utils/Delta.js +++ b/src/utils/Delta.js @@ -1,5 +1,6 @@ import * as object from 'lib0/object' import * as fun from 'lib0/function' +import * as traits from 'lib0/traits' /** * @template {string|Array|{[key: string]: any}} Content @@ -29,9 +30,20 @@ export class InsertOp { this.attribution = attribution } + get length () { + return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1 + } + toJSON () { return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({})) } + + /** + * @param {InsertOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution) + } } export class DeleteOp { @@ -42,9 +54,20 @@ export class DeleteOp { this.delete = len } + get length () { + return 0 + } + toJSON () { return { delete: this.delete } } + + /** + * @param {DeleteOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.delete === other.delete + } } export class RetainOp { @@ -59,16 +82,48 @@ export class RetainOp { this.attribution = attribution } + get length () { + return this.retain + } + toJSON () { return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {}) } + + /** + * @param {RetainOp} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.retain === other.retain && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution) + } } /** - * @template {string|Array|{[key: string]: any}} Content + * @typedef {Array} ArrayDeltaContent */ -export class Delta { - constructor () { + +/** + * @typedef {string | { [key: string]: any }} TextDeltaContent + */ + +/** + * @typedef {{ array: ArrayDeltaContent, text: TextDeltaContent, custom: string|Array|{[key:string]:any}}} DeltaTypeMapper + */ + +/** + * @typedef {(TextDelta | ArrayDelta)} Delta + */ + +/** + * @template {'array' | 'text' | 'custom'} Type + * @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]] + */ +export class AbstractDelta { + /** + * @param {Type} type + */ + constructor (type) { + this.type = type /** * @type {Array>} */ @@ -76,48 +131,49 @@ export class Delta { } /** - * @param {Delta} d + * @template {DeltaTypeMapper[Type]} MContent + * @param {(d:DeltaOp)=>DeltaOp} f + * @return {DeltaBuilder} + */ + map (f) { + const d = /** @type {DeltaBuilder} */ (new /** @type {any} */ (this.constructor)(this.type)) + d.ops = this.ops.map(f) + // @ts-ignore + d._lastOp = d.ops[d.ops.length - 1] ?? null + return d + } + + /** + * @param {(d:DeltaOp,index:number)=>void} f + */ + forEach (f) { + for ( + let i = 0, index = 0, op = this.ops[i]; + i < this.ops.length; + i++, index += op.length, op = this.ops[i] + ) { + f(op, index) + } + } + + /** + * @param {AbstractDelta} other * @return {boolean} */ - equals (d) { - return this.ops.length === d.ops.length && this.ops.every((op, i) => { - const dop = d.ops[i] - if (op.constructor !== dop.constructor) return false - switch (op.constructor) { - case DeleteOp: { - if (/** @type {DeleteOp} */ (op).delete !== /** @type {DeleteOp} */ (dop).delete) { - return false - } - break - } - case InsertOp: { - if ( - !fun.equalityDeep(/** @type {InsertOp} */ (op).insert, /** @type {InsertOp} */ (dop).insert) - || !fun.equalityDeep(/** @type {InsertOp} */ (op).attributes, /** @type {InsertOp} */ (dop).attributes) - || !fun.equalityDeep(/** @type {InsertOp} */ (op).attribution, /** @type {InsertOp} */ (dop).attribution) - ) { - return false - } - break - } - case RetainOp: { - if ( - /** @type {RetainOp} */ (op).retain !== /** @type {RetainOp} */ (dop).retain - || !fun.equalityDeep(/** @type {RetainOp} */ (op).attributes, /** @type {RetainOp} */ (dop).attributes) - || !fun.equalityDeep(/** @type {RetainOp} */ (op).attribution, /** @type {RetainOp} */ (dop).attribution) - ) { - return false - } - break - } - } - return true - }) + equals (other) { + return this[traits.EqualityTraitSymbol](other) } toJSON () { return { ops: this.ops.map(o => o.toJSON()) } } + + /** + * @param {AbstractDelta} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.type === other.type && fun.equalityDeep(this.ops, other.ops) + } } /** @@ -134,12 +190,16 @@ const mergeAttrs = (a, b) => { } /** - * @template {string|Array|{[key: string]: any}} Content - * @extends Delta + * @template {'array' | 'text' | 'custom'} [Type='custom'] + * @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]] + * @extends AbstractDelta */ -export class DeltaBuilder extends Delta { - constructor () { - super() +export class DeltaBuilder extends AbstractDelta { + /** + * @param {Type} type + */ + constructor (type) { + super(type) /** * @type {FormattingAttributes?} */ @@ -185,10 +245,10 @@ export class DeltaBuilder extends Delta { const mergedAttribution = attribution == null ? this.usedAttribution : mergeAttrs(this.usedAttribution, attribution) if (this._lastOp instanceof InsertOp && (mergedAttributes === this._lastOp.attributes || fun.equalityDeep(mergedAttributes, this._lastOp.attributes)) && (mergedAttribution === this._lastOp.attribution || fun.equalityDeep(mergedAttribution, this._lastOp.attribution))) { if (insert.constructor === String) { - // @ts-ignore + // @ts-ignore this._lastOp.insert += insert } else if (insert.constructor === Array && this._lastOp.insert.constructor === Array) { - // @ts-ignore + // @ts-ignore this._lastOp.insert.push(...insert) } else { this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution)) @@ -230,31 +290,39 @@ export class DeltaBuilder extends Delta { } /** - * @return {Delta} + * @return {AbstractDelta} */ done () { return this } } -export const create = () => new DeltaBuilder() +/** + * @template {ArrayDeltaContent} [Content=ArrayDeltaContent] + * @extends DeltaBuilder<'array',Content> + */ +export class ArrayDelta extends DeltaBuilder { + constructor () { + super('array') + } +} /** - * @typedef {string | { [key: string]: any }} TextDeltaContent + * @template {TextDeltaContent} [Content=TextDeltaContent] + * @extends DeltaBuilder<'text',Content> */ +export class TextDelta extends DeltaBuilder { + constructor () { + super('text') + } +} /** - * @template {TextDeltaContent} Content - * @return {DeltaBuilder} + * @return {TextDelta} */ -export const createTextDelta = () => new DeltaBuilder() +export const createTextDelta = () => new TextDelta() /** - * @typedef {Array} ArrayDeltaContent + * @return {ArrayDelta} */ - -/** - * @template {ArrayDeltaContent} Content - * @return {DeltaBuilder} - */ -export const createArrayDelta = () => new DeltaBuilder() +export const createArrayDelta = () => new ArrayDelta() diff --git a/src/utils/IdSet.js b/src/utils/IdSet.js index 96f27a97..1b9a40d6 100644 --- a/src/utils/IdSet.js +++ b/src/utils/IdSet.js @@ -414,7 +414,6 @@ export const createInsertionSetFromStructStore = ss => { return idset } - /** * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder * @param {IdSet} idSet diff --git a/test.html b/test.html index 28bf9ad3..9273884e 100644 --- a/test.html +++ b/test.html @@ -135,6 +135,9 @@ "lib0/symbol.js": "./node_modules/lib0/symbol.js", "lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs", "lib0/symbol": "./node_modules/lib0/symbol.js", + "lib0/traits.js": "./node_modules/lib0/traits.js", + "lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs", + "lib0/traits": "./node_modules/lib0/traits.js", "lib0/testing.js": "./node_modules/lib0/testing.js", "lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs", "lib0/testing": "./node_modules/lib0/testing.js", @@ -296,6 +299,9 @@ "lib0/symbol.js": "./node_modules/lib0/symbol.js", "lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs", "lib0/symbol": "./node_modules/lib0/symbol.js", + "lib0/traits.js": "./node_modules/lib0/traits.js", + "lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs", + "lib0/traits": "./node_modules/lib0/traits.js", "lib0/testing.js": "./node_modules/lib0/testing.js", "lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs", "lib0/testing": "./node_modules/lib0/testing.js", diff --git a/tests/IdMap.tests.js b/tests/IdMap.tests.js index 79809e2e..cf7ddae7 100644 --- a/tests/IdMap.tests.js +++ b/tests/IdMap.tests.js @@ -1,6 +1,6 @@ import * as t from 'lib0/testing' import * as idmap from '../src/utils/IdMap.js' -import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution, validateIdMap } from './testHelper.js' +import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution } from './testHelper.js' import * as YY from '../src/internals.js' /** diff --git a/tests/delta.tests.js b/tests/delta.tests.js index 0c43c327..0237c974 100644 --- a/tests/delta.tests.js +++ b/tests/delta.tests.js @@ -5,7 +5,7 @@ import * as delta from '../src/utils/Delta.js' * @param {t.TestCase} _tc */ export const testDelta = _tc => { - const d = delta.create().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done() + const d = delta.createTextDelta().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done() t.compare(d.toJSON().ops, [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }]) } @@ -13,7 +13,7 @@ export const testDelta = _tc => { * @param {t.TestCase} _tc */ export const testDeltaMerging = _tc => { - const d = delta.create() + const d = delta.createTextDelta() .insert('hello') .insert('world') .insert(' ', { italic: true }) diff --git a/tests/doc.tests.js b/tests/doc.tests.js index b55ee861..80db1b28 100644 --- a/tests/doc.tests.js +++ b/tests/doc.tests.js @@ -19,7 +19,6 @@ export const testAfterTransactionRecursion = _tc => { }, 'test') } - /** * @param {t.TestCase} _tc */ @@ -39,7 +38,7 @@ export const testFindTypeInOtherDoc = _tc => { const ydoc = /** @type {Y.Doc} */ (ytype.doc) if (ytype._item === null) { /** - * If is a root type, we need to find the root key in the original ydoc + * If is a root type, we need to find the root key in the original ydoc * and use it to get the type in the other ydoc. */ const rootKey = Array.from(ydoc.share.keys()).find( @@ -68,7 +67,6 @@ export const testFindTypeInOtherDoc = _tc => { t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null) } - /** * @param {t.TestCase} _tc */ diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index f508f03c..faad66c4 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -528,8 +528,8 @@ export const testAttributedContent = _tc => { yarray.delete(0, 1) yarray.insert(1, [42]) }) - let expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] }) - let attributedContent = yarray.getContent(attributionManager) + const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] }) + const attributedContent = yarray.getContent(attributionManager) console.log(attributedContent.toJSON().ops) t.assert(attributedContent.equals(expectedContent)) }) diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index fbd64cd4..ff056e9e 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -1,6 +1,5 @@ import * as Y from '../src/index.js' import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line -import * as delta from '../src/utils/Delta.js' import { compareIDs, noAttributionsManager, @@ -631,22 +630,22 @@ export const testAttributedContent = _tc => { }) t.group('initial value', () => { ymap.set('test', 42) - let expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } } - let attributedContent = ymap.getContent(attributionManager) + const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } } + const attributedContent = ymap.getContent(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) t.group('overwrite value', () => { ymap.set('test', 'fourtytwo') - let expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } } - let attributedContent = ymap.getContent(attributionManager) + const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } } + const attributedContent = ymap.getContent(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) t.group('delete value', () => { ymap.delete('test') - let expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } } - let attributedContent = ymap.getContent(attributionManager) + const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } } + const attributedContent = ymap.getContent(attributionManager) console.log(attributedContent) t.compare(expectedContent, attributedContent) }) diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index ebde95f5..95bb3837 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -2316,15 +2316,15 @@ export const testAttributedContent = _tc => { }) t.group('insert / delete / format', () => { ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }]) - let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!') - let attributedContent = ytext.getContent(attributionManager) + const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!') + const attributedContent = ytext.getContent(attributionManager) console.log(attributedContent.toJSON().ops) t.assert(attributedContent.equals(expectedContent)) }) t.group('unformat', () => { - ytext.applyDelta([{retain: 5, attributes: { italic: null }}]) - let expectedContent = delta.create().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!') - let attributedContent = ytext.getContent(attributionManager) + ytext.applyDelta([{ retain: 5, attributes: { italic: null } }]) + const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!') + const attributedContent = ytext.getContent(attributionManager) console.log(attributedContent.toJSON().ops) t.assert(attributedContent.equals(expectedContent)) }) @@ -2355,9 +2355,9 @@ export const testAttributedDiffing = _tc => { // implementations is the TwosetAttributionManager const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions) // we render the attributed content with the attributionManager - let attributedContent = ytext.getContent(attributionManager) + const attributedContent = ytext.getContent(attributionManager) console.log(JSON.stringify(attributedContent.toJSON().ops, null, 2)) - let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!') + const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!') t.assert(attributedContent.equals(expectedContent)) console.log(Y.encodeIdMap(attributedInsertions).length) } @@ -2588,14 +2588,14 @@ const checkResult = result => { */ const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d - t.info('length of text = ' + result.users[i-1].getText('text').length) + t.info('length of text = ' + result.users[i - 1].getText('text').length) t.measureTime('original toDelta perf', () => { - result.users[i-1].getText('text').toDelta().map(typeToObject) + result.users[i - 1].getText('text').toDelta().map(typeToObject) }) t.measureTime('getContent(attributionManager) performance)', () => { - result.users[i-1].getText('text').getContent() + result.users[i - 1].getText('text').getContent() }) - const p1 = result.users[i-1].getText('text').toDelta().map(typeToObject) + const p1 = result.users[i - 1].getText('text').toDelta().map(typeToObject) const p2 = result.users[i].getText('text').toDelta().map(typeToObject) t.compare(p1, p2) } @@ -2629,7 +2629,7 @@ export const testAttributionManagerDefaultPerformance = tc => { ytext.insert(index, content) } } - t.info(`number of changes: ${N/1000}k`) + t.info(`number of changes: ${N / 1000}k`) t.info(`length of text: ${ytext.length}`) const M = 100 t.measureTime(`original toString perf `, () => { diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index faaf65c4..72dd9dc4 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -243,8 +243,8 @@ export const testFragmentAttributedContent = _tc => { yfragment.delete(0, 1) yfragment.insert(1, [elem3]) }) - let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) - let attributedContent = yfragment.getContent(attributionManager) + const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) + const attributedContent = yfragment.getContent(attributionManager) console.log(attributedContent.children.toJSON().ops) t.assert(attributedContent.children.equals(expectedContent)) t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON()) @@ -257,8 +257,8 @@ export const testFragmentAttributedContent = _tc => { export const testElementAttributedContent = _tc => { const ydoc = new Y.Doc({ gc: false }) const yelement = ydoc.getXmlElement('p') - const elem1 = new Y.XmlElement('span') - const elem2 = new Y.XmlText('hello') + const elem1 = new Y.XmlText('hello') + const elem2 = new Y.XmlElement('span') const elem3 = new Y.XmlText('world') yelement.insert(0, [elem1, elem2]) let attributionManager = Y.noAttributionsManager @@ -272,12 +272,28 @@ export const testElementAttributedContent = _tc => { yelement.insert(1, [elem3]) yelement.setAttribute('key', '42') }) - let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) - let attributedContent = yelement.getContent(attributionManager) + const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) + const attributedContent = yelement.getContent(attributionManager) console.log('children', attributedContent.children.toJSON().ops) console.log('attributes', attributedContent.attributes) t.assert(attributedContent.children.equals(expectedContent)) t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } }) + t.group('test getContentDeep', () => { + const expectedContent = delta.createArrayDelta().insert( + [delta.createTextDelta().insert('hello', null, { delete: [] })], + null, + { delete: [] } + ).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }]) + .insert([ + delta.createTextDelta().insert('world', null, { insert: [] }) + ], null, { insert: [] }) + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', JSON.stringify(attributedContent.children.toJSON().ops, null, 2)) + console.log('cs expec', JSON.stringify(expectedContent.toJSON().ops, null, 2)) + console.log('attributes', attributedContent.attributes) + t.assert(attributedContent.children.equals(expectedContent)) + t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } }) + t.assert(attributedContent.nodeName === 'UNDEFINED') + }) }) } -