mirror of
https://github.com/yjs/yjs.git
synced 2025-12-16 03:37:50 +01:00
fixes and more tests for delta representation on abstract types
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "14.0.0-10",
|
"version": "14.0.0-10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.2.115-1"
|
"lib0": "^0.2.115-2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.1",
|
"@types/node": "^22.14.1",
|
||||||
@@ -3478,9 +3478,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lib0": {
|
"node_modules/lib0": {
|
||||||
"version": "0.2.115-1",
|
"version": "0.2.115-2",
|
||||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-1.tgz",
|
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-2.tgz",
|
||||||
"integrity": "sha512-mmQ4Pk/wZBsjdGMUJtXBhPsqPZof6Eh9sqrApA2Ufqe2eFYWW4yQPZWdf5/ak+dXsRlbslLHrGAn7+MeOY3TGA==",
|
"integrity": "sha512-LBe5bPJTGG9/7F+1Ax1moAHrHJ1TaaTQWw7J2t6L19yHN3U6uHBSUcIRsews1f6J7fiKWwoiNohGCebd96lnig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic.js": "^0.2.4"
|
"isomorphic.js": "^0.2.4"
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://docs.yjs.dev",
|
"homepage": "https://docs.yjs.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.2.115-1"
|
"lib0": "^0.2.115-2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.1",
|
"@types/node": "^22.14.1",
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ export class Item extends AbstractStruct {
|
|||||||
* @param {ID | null} origin
|
* @param {ID | null} origin
|
||||||
* @param {Item | null} right
|
* @param {Item | null} right
|
||||||
* @param {ID | null} rightOrigin
|
* @param {ID | null} rightOrigin
|
||||||
* @param {YType__|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
|
* @param {AbstractType<any,any>|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
|
||||||
* @param {string | null} parentSub
|
* @param {string | null} parentSub
|
||||||
* @param {AbstractContent} content
|
* @param {AbstractContent} content
|
||||||
*/
|
*/
|
||||||
@@ -309,7 +309,7 @@ export class Item extends AbstractStruct {
|
|||||||
*/
|
*/
|
||||||
this.rightOrigin = rightOrigin
|
this.rightOrigin = rightOrigin
|
||||||
/**
|
/**
|
||||||
* @type {YType__|ID|null}
|
* @type {AbstractType<any,any>|ID|null}
|
||||||
*/
|
*/
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export const callTypeObservers = (type, transaction, event) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Yjs Type class
|
* Abstract Yjs Type class
|
||||||
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=delta.Delta<any,any,any,any,any>]
|
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=any]
|
||||||
* @template {AbstractType<any,any>} [Self=any]
|
* @template {AbstractType<any,any>} [Self=any]
|
||||||
*/
|
*/
|
||||||
export class AbstractType {
|
export class AbstractType {
|
||||||
@@ -392,9 +392,11 @@ export class AbstractType {
|
|||||||
* Must be implemented by each type.
|
* Must be implemented by each type.
|
||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} _parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, _parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
|
const event = new YEvent(/** @type {any} */ (this), transaction, parentSubs)
|
||||||
|
callTypeObservers(/** @type {any} */ (this), transaction, event)
|
||||||
if (!transaction.local && this._searchMarker) {
|
if (!transaction.local && this._searchMarker) {
|
||||||
this._searchMarker.length = 0
|
this._searchMarker.length = 0
|
||||||
}
|
}
|
||||||
@@ -738,6 +740,19 @@ export class AbstractType {
|
|||||||
error.unexpectedCase()
|
error.unexpectedCase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const op of d.attrs) {
|
||||||
|
if (delta.$insertOp.check(op)) {
|
||||||
|
typeMapSet(transaction, /** @type {any} */ (this), op.key, op.value)
|
||||||
|
} else if (delta.$deleteOp.check(op)) {
|
||||||
|
typeMapDelete(transaction, /** @type {any} */ (this), op.key)
|
||||||
|
} else {
|
||||||
|
const sub = typeMapGet(/** @type {any} */ (this), op.key)
|
||||||
|
if (!(sub instanceof AbstractType)) {
|
||||||
|
error.unexpectedCase()
|
||||||
|
}
|
||||||
|
sub.applyDelta(op.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1201,7 +1216,7 @@ export const typeMapDelete = (transaction, parent, key) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {YType_} parent
|
* @param {AbstractType} parent
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {_YValue} value
|
* @param {_YValue} value
|
||||||
*
|
*
|
||||||
@@ -1244,7 +1259,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {YType_} parent
|
* @param {AbstractType<any,any>} parent
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
typeListDelete,
|
typeListDelete,
|
||||||
typeListMap,
|
typeListMap,
|
||||||
YArrayRefID,
|
YArrayRefID,
|
||||||
callTypeObservers,
|
|
||||||
transact,
|
transact,
|
||||||
warnPrematureAccess,
|
warnPrematureAccess,
|
||||||
typeListSlice,
|
typeListSlice,
|
||||||
@@ -101,17 +100,6 @@ export class YArray extends AbstractType {
|
|||||||
return this._length
|
return this._length
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates YArrayEvent and calls observers.
|
|
||||||
*
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
||||||
*/
|
|
||||||
_callObserver (transaction, parentSubs) {
|
|
||||||
super._callObserver(transaction, parentSubs)
|
|
||||||
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts new content at an index.
|
* Inserts new content at an index.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
YEvent,
|
|
||||||
AbstractType,
|
AbstractType,
|
||||||
typeMapDelete,
|
typeMapDelete,
|
||||||
typeMapSet,
|
typeMapSet,
|
||||||
@@ -11,10 +10,9 @@ import {
|
|||||||
typeMapHas,
|
typeMapHas,
|
||||||
createMapIterator,
|
createMapIterator,
|
||||||
YMapRefID,
|
YMapRefID,
|
||||||
callTypeObservers,
|
|
||||||
transact,
|
transact,
|
||||||
warnPrematureAccess,
|
warnPrematureAccess,
|
||||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as iterator from 'lib0/iterator'
|
import * as iterator from 'lib0/iterator'
|
||||||
@@ -80,16 +78,6 @@ export class YMap extends AbstractType {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates YMapEvent and calls observers.
|
|
||||||
*
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
||||||
*/
|
|
||||||
_callObserver (transaction, parentSubs) {
|
|
||||||
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms this Shared Type to a JSON object.
|
* Transforms this Shared Type to a JSON object.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
YEvent,
|
|
||||||
AbstractType,
|
AbstractType,
|
||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
getState,
|
getState,
|
||||||
createID,
|
createID,
|
||||||
YTextRefID,
|
YTextRefID,
|
||||||
callTypeObservers,
|
|
||||||
transact,
|
transact,
|
||||||
ContentEmbed,
|
ContentEmbed,
|
||||||
GC,
|
GC,
|
||||||
@@ -705,8 +703,6 @@ export class YText extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
super._callObserver(transaction, parentSubs)
|
super._callObserver(transaction, parentSubs)
|
||||||
const event = new YEvent(/** @type {YText<any>} */ (this), transaction, parentSubs)
|
|
||||||
callTypeObservers(/** @type {YText<any>} */ (this), transaction, event)
|
|
||||||
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
||||||
if (!transaction.local && this._hasFormatting) {
|
if (!transaction.local && this._hasFormatting) {
|
||||||
transaction._needFormattingCleanup = true
|
transaction._needFormattingCleanup = true
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
typeListDelete,
|
typeListDelete,
|
||||||
typeListToArray,
|
typeListToArray,
|
||||||
YXmlFragmentRefID,
|
YXmlFragmentRefID,
|
||||||
callTypeObservers,
|
|
||||||
transact,
|
transact,
|
||||||
typeListGet,
|
typeListGet,
|
||||||
typeListSlice,
|
typeListSlice,
|
||||||
@@ -107,16 +106,6 @@ export class YXmlFragment extends AbstractType {
|
|||||||
return this._prelimContent === null ? this._length : this._prelimContent.length
|
return this._prelimContent === null ? this._length : this._prelimContent.length
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates YXmlEvent and calls observers.
|
|
||||||
*
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
||||||
*/
|
|
||||||
_callObserver (transaction, parentSubs) {
|
|
||||||
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the string representation of all the children of this YXmlFragment.
|
* Get the string representation of all the children of this YXmlFragment.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import * as delta from 'lib0/delta' // eslint-disable-line
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template {_YType} Target
|
* @template {AbstractType<any,any>} Target
|
||||||
* YEvent describes the changes on a YType.
|
* YEvent describes the changes on a YType.
|
||||||
*/
|
*/
|
||||||
export class YEvent {
|
export class YEvent {
|
||||||
|
|||||||
166
tests/delta.tests.js
Normal file
166
tests/delta.tests.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import * as Y from '../src/index.js'
|
||||||
|
import * as delta from 'lib0/delta'
|
||||||
|
import * as t from 'lib0/testing'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delta is a versatyle format enabling you to efficiently describe changes. It is part of lib0, so
|
||||||
|
* that non-yjs applications can use it without consuming the full Yjs package. It is well suited
|
||||||
|
* for efficiently describing state & changesets.
|
||||||
|
*
|
||||||
|
* Assume we start with the text "hello world". Now we want to delete " world" and add an
|
||||||
|
* exclamation mark. The final content should be "hello!" ("hello world" => "hello!")
|
||||||
|
*
|
||||||
|
* In most editors, you would describe the necessary changes as replace operations using indexes.
|
||||||
|
* However, this might become ambiguous when many changes are involved.
|
||||||
|
*
|
||||||
|
* - delete range 5-11
|
||||||
|
* - insert "!" at position 11
|
||||||
|
*
|
||||||
|
* Using the delta format, you can describe the changes similar to what you would do in an text editor.
|
||||||
|
* The "|" describes the current cursor position.
|
||||||
|
*
|
||||||
|
* - d.retain(5) - "|hello world" => "hello| world" - jump over the next five characters
|
||||||
|
* - d.delete(6) - "hello| world" => "hello|" - delete the next 6 characres
|
||||||
|
* - d.insert('!') - "hello!|" - insert "!" at the current position
|
||||||
|
* => compact form: d.retain(5).delete(6).insert('!')
|
||||||
|
*
|
||||||
|
* You can also apply the changes in two distinct steps and then rebase the op so that you can apply
|
||||||
|
* them in two distinct steps.
|
||||||
|
* - delete " world": d1 = delta.create().retain(5).delete(6)
|
||||||
|
* - insert "!": d2 = delta.create().retain(11).insert('!')
|
||||||
|
* - rebase d2 on-top of d1: d2.rebase(d1) == delta.create().retain(5).insert('!')
|
||||||
|
* - merge into a single change: d1.apply(d2) == delta.create().retain(5).delete(6).insert(!)
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} _tc
|
||||||
|
*/
|
||||||
|
export const testDeltaBasics = _tc => {
|
||||||
|
// the state of our text document
|
||||||
|
const state = delta.create().insert('hello world')
|
||||||
|
// describe changes: delete " world" & insert "!"
|
||||||
|
const change = delta.create().retain(5).delete(6).insert('!')
|
||||||
|
// apply changes to state
|
||||||
|
state.apply(change)
|
||||||
|
// compare state to expected state
|
||||||
|
t.assert(state.equals(delta.create().insert('hello!')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deltas can describe changes on attributes and children. Textual insertions are children. But we
|
||||||
|
* may also insert json-objects and other deltas as children.
|
||||||
|
* Key-value pairs can be represented as attributes. This "convoluted" changeset enables us to
|
||||||
|
* describe many changes in the same breath:
|
||||||
|
*
|
||||||
|
* delta.create().set('a', 42).retain(5).delete(6).insert('!').unset('b')
|
||||||
|
*
|
||||||
|
* @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' }])
|
||||||
|
// iterate through attribute changes
|
||||||
|
for (const attrChange of change.attrs) {
|
||||||
|
if (delta.$insertOp.check(attrChange)) {
|
||||||
|
console.log(`set ${attrChange.key} to ${attrChange.value}`)
|
||||||
|
} else if (delta.$deleteOp.check(attrChange)) {
|
||||||
|
console.log(`delete ${attrChange.key}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// iterate through child changes
|
||||||
|
for (const childChange of change.children) {
|
||||||
|
if (delta.$retainOp.check(childChange)) {
|
||||||
|
console.log(`retain ${childChange.retain} child items`)
|
||||||
|
} else if (delta.$deleteOp.check(childChange)) {
|
||||||
|
console.log(`delete ${childChange.delete} child items`)
|
||||||
|
} else if (delta.$insertOp.check(childChange)) {
|
||||||
|
console.log(`insert child items:`, childChange.insert)
|
||||||
|
} else if (delta.$textOp.check(childChange)) {
|
||||||
|
console.log(`insert textual content`, childChange.insert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new delta defines changes on attributes (key-value) and child elements (list & text), but can
|
||||||
|
* also be used to describe the current state of a document.
|
||||||
|
*
|
||||||
|
* 1. apply a delta to change a yjs type
|
||||||
|
* 2. observe deltas to read the differences
|
||||||
|
* 3. merge deltas to reflect multiple changes in a single delta
|
||||||
|
* 4. All Yjs types fully support the delta format. It is no longer necessary to define the type (such as Y.Array)
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} _tc
|
||||||
|
*/
|
||||||
|
export const testBasics = _tc => {
|
||||||
|
const ydoc = new Y.Doc()
|
||||||
|
const ytype = ydoc.get('my data')
|
||||||
|
/**
|
||||||
|
* @type {delta.Delta<any,{ a: number }, { my: string }, string>}
|
||||||
|
*/
|
||||||
|
let observedDelta = delta.create()
|
||||||
|
ytype.observe(event => {
|
||||||
|
observedDelta = event.deltaDeep
|
||||||
|
console.log('ytype changed:', observedDelta.toJSON())
|
||||||
|
})
|
||||||
|
// define a change: set attribute: a=42
|
||||||
|
const attrChange = delta.create().set('a', 42).done()
|
||||||
|
// define a change: insert textual content and an object
|
||||||
|
const childChange = delta.create().insert('hello').insert([{ my: 'object' }]).done()
|
||||||
|
// merge changes
|
||||||
|
const mergedChanges = delta.create(delta.$deltaAny).apply(attrChange).apply(childChange).done()
|
||||||
|
console.log('merged changes: ', mergedChanges.toJSON())
|
||||||
|
ytype.applyDelta(mergedChanges)
|
||||||
|
// the observed change should equal the applied change
|
||||||
|
t.assert(observedDelta.equals(mergedChanges))
|
||||||
|
// read the current state of the yjs types as a delta
|
||||||
|
const currState = ytype.getContentDeep()
|
||||||
|
t.assert(currState.equals(mergedChanges)) // equal to the changes that we applied
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deltas allow us to describe the differences between two Yjs documents though "Attributions".
|
||||||
|
*
|
||||||
|
* - We can attribute changes to a user, or a group of users
|
||||||
|
* - There are 'insert', 'delete', and 'format' attributions
|
||||||
|
* - When we render attributions, we render inserted & deleted content as an insertions with special
|
||||||
|
* attributes which allow you to..
|
||||||
|
* -- Render deleted content using a strikethrough: I.e. `hello w̶o̶r̶l̶d̶!`
|
||||||
|
* -- Render attributed insertions using a background color.
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} _tc
|
||||||
|
*/
|
||||||
|
export const testAttributions = _tc => {
|
||||||
|
const ydocV1 = new Y.Doc()
|
||||||
|
const ytypeV1 = ydocV1.get('txt')
|
||||||
|
ytypeV1.applyDelta(delta.create().insert('hello world'))
|
||||||
|
// create a new version with updated content
|
||||||
|
const ydoc = new Y.Doc()
|
||||||
|
Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocV1))
|
||||||
|
const ytype = ydoc.get('txt')
|
||||||
|
// delete " world" and insert exclamation mark "!".
|
||||||
|
ytype.applyDelta(delta.create().retain(5).delete(6).insert('!'))
|
||||||
|
const am = Y.createAttributionManagerFromDiff(ydocV1, ydoc)
|
||||||
|
// get the attributed differences
|
||||||
|
const attributedContent = ytype.getContent(am)
|
||||||
|
console.log('attributed content', attributedContent.toJSON())
|
||||||
|
t.assert(attributedContent.equals(delta.create().insert('hello').insert(' world', null, { delete: [] }).insert('!', null, { insert: [] })))
|
||||||
|
// for editor bindings, it is also necessary to observe changes and get the attributed changes
|
||||||
|
ytype.observe(event => {
|
||||||
|
const attributedChange = event.getDelta(am)
|
||||||
|
console.log('the attributed change', attributedChange.toJSON())
|
||||||
|
t.assert(attributedChange.equals(delta.create().retain(11).insert('!', null, { insert: [] })))
|
||||||
|
const unattributedChange = event.delta
|
||||||
|
console.log('the UNattributed change', unattributedChange.toJSON())
|
||||||
|
t.assert(unattributedChange.equals(delta.create().retain(5).insert('!')))
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Content now has different representations.
|
||||||
|
* - The UNattributed representation renders the latest state, without history.
|
||||||
|
* - The attributed representation renders the differences.
|
||||||
|
*
|
||||||
|
* Attributed: 'hello<delete> world</delete><insert>!</insert>'
|
||||||
|
* UNattributed: 'world!'
|
||||||
|
*/
|
||||||
|
// Apply a change to the attributed content
|
||||||
|
ytype.applyDelta(delta.create().retain(11).insert('!'), am)
|
||||||
|
// // Equivalent to applying a change to the UNattributed content:
|
||||||
|
// ytype.applyDelta(delta.create().retain(5).insert('!'))
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import * as relativePositions from './relativePositions.tests.js'
|
|||||||
import * as idset from './IdSet.tests.js'
|
import * as idset from './IdSet.tests.js'
|
||||||
import * as idmap from './IdMap.tests.js'
|
import * as idmap from './IdMap.tests.js'
|
||||||
import * as attribution from './attribution.tests.js'
|
import * as attribution from './attribution.tests.js'
|
||||||
|
import * as delta from './delta.tests.js'
|
||||||
|
|
||||||
import { runTests } from 'lib0/testing'
|
import { runTests } from 'lib0/testing'
|
||||||
import { isBrowser, isNode } from 'lib0/environment'
|
import { isBrowser, isNode } from 'lib0/environment'
|
||||||
@@ -24,7 +25,7 @@ if (isBrowser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tests = {
|
const tests = {
|
||||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, idset, idmap, attribution
|
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, idset, idmap, attribution, delta
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user