reimplemented WeakLink as an AbstractType

This commit is contained in:
Bartosz Sypytkowski 2023-06-09 15:55:25 +02:00
parent 7b0d5fede5
commit 2c60c4a130
7 changed files with 251 additions and 257 deletions

View File

@ -40,6 +40,5 @@ export * from './structs/ContentJSON.js'
export * from './structs/ContentAny.js' export * from './structs/ContentAny.js'
export * from './structs/ContentString.js' export * from './structs/ContentString.js'
export * from './structs/ContentType.js' export * from './structs/ContentType.js'
export * from './structs/ContentLink.js'
export * from './structs/Item.js' export * from './structs/Item.js'
export * from './structs/Skip.js' export * from './structs/Skip.js'

View File

@ -1,183 +0,0 @@
import { decoding, encoding, error } from 'lib0'
import {
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore, // eslint-disable-line
YWeakLink,
AbstractType,
getItemCleanStart,
createID,
getItemCleanEnd
} from '../internals.js'
export class ContentLink {
/**
* @param {YWeakLink<any>} link
*/
constructor (link) {
this.link = link
/**
* @type {Item|null}
*/
this._item = null
}
/**
* @return {number}
*/
getLength () {
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
return [this.link]
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentLink}
*/
copy () {
return new ContentLink(this.link)
}
/**
* @param {number} offset
* @return {ContentLink}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {ContentLink} right
* @return {boolean}
*/
mergeWith (right) {
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {
let sourceItem = this.link.item !== null ? this.link.item : getItemCleanStart(transaction, this.link.id)
if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
// for maps, advance to most recent item
while (sourceItem.right !== null) {
sourceItem = sourceItem.right
}
}
if (!sourceItem.deleted && sourceItem.length > 1) {
sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
}
this.link.item = sourceItem
this._item = item
if (!sourceItem.deleted) {
const src = /** @type {Item} */ (sourceItem)
if (src.linkedBy === null) {
src.linkedBy = new Set()
}
src.linkedBy.add(item)
}
}
/**
* @param {Transaction} transaction
*/
delete (transaction) {
if (this._item !== null && this.link !== null && this.link.item !== null && !this.link.item.deleted) {
const item = /** @type {Item} */ (this.link.item)
if (item.linkedBy !== null) {
item.linkedBy.delete(this._item)
}
this.link.item = null
}
}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset
*/
write (encoder, offset) {
const flags = 0 // flags that could be used in the future
encoding.writeUint8(encoder.restEncoder, flags)
encoder.writeLeftID(this.link.id)
}
/**
* @return {number}
*/
getRef () {
return 11
}
}
/**
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
* @return {ContentLink}
*/
export const readContentWeakLink = decoder => {
const flags = decoding.readUint8(decoder.restDecoder)
const id = decoder.readLeftID()
return new ContentLink(new YWeakLink(id, null))
}
const lengthExceeded = error.create('Length exceeded!')
/**
* Returns a {WeakLink} to an YArray element at given index.
*
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {number} index
* @return {YWeakLink<any>}
*/
export const arrayWeakLink = (transaction, parent, index) => {
let item = parent._start
for (; item !== null; item = item.right) {
if (!item.deleted && item.countable) {
if (index < item.length) {
if (index > 0) {
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index))
}
if (item.length > 1) {
item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock + 1))
}
return new YWeakLink(item.id, item)
}
index -= item.length
}
}
throw lengthExceeded
}
/**
* Returns a {WeakLink} to an YMap element at given key.
*
* @param {AbstractType<any>} parent
* @param {string} key
* @return {YWeakLink<any>|undefined}
*/
export const mapWeakLink = (parent, key) => {
const item = parent._map.get(key)
if (item !== undefined) {
return new YWeakLink(item.id, item)
} else {
return undefined
}
}

View File

@ -7,7 +7,8 @@ import {
readYXmlFragment, readYXmlFragment,
readYXmlHook, readYXmlHook,
readYXmlText, readYXmlText,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType, // eslint-disable-line
readYWeakLink
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error' import * as error from 'lib0/error'
@ -23,7 +24,8 @@ export const typeRefs = [
readYXmlElement, readYXmlElement,
readYXmlFragment, readYXmlFragment,
readYXmlHook, readYXmlHook,
readYXmlText readYXmlText,
readYWeakLink
] ]
export const YArrayRefID = 0 export const YArrayRefID = 0
@ -33,6 +35,7 @@ export const YXmlElementRefID = 3
export const YXmlFragmentRefID = 4 export const YXmlFragmentRefID = 4
export const YXmlHookRefID = 5 export const YXmlHookRefID = 5
export const YXmlTextRefID = 6 export const YXmlTextRefID = 6
export const YWeakLinkRefID = 7
/** /**
* @private * @private
@ -104,6 +107,7 @@ export class ContentType {
* @param {Transaction} transaction * @param {Transaction} transaction
*/ */
delete (transaction) { delete (transaction) {
this.type._delete(transaction) // call custom destructor on AbstractType
let item = this.type._start let item = this.type._start
while (item !== null) { while (item !== null) {
if (!item.deleted) { if (!item.deleted) {

View File

@ -18,15 +18,13 @@ import {
readContentString, readContentString,
readContentEmbed, readContentEmbed,
readContentDoc, readContentDoc,
readContentWeakLink,
createID, createID,
readContentFormat, readContentFormat,
readContentType, readContentType,
addChangedTypeToTransaction, addChangedTypeToTransaction,
isDeleted, isDeleted,
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
YWeakLink, YWeakLink
ContentLink
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error' import * as error from 'lib0/error'
@ -302,7 +300,7 @@ export class Item extends AbstractStruct {
* If this item was referenced by other weak links, here we keep the references * If this item was referenced by other weak links, here we keep the references
* to these weak refs. * to these weak refs.
* *
* @type {Set<Item> | null} * @type {Set<YWeakLink<any>> | null}
*/ */
this.linkedBy = null this.linkedBy = null
/** /**
@ -386,10 +384,10 @@ export class Item extends AbstractStruct {
if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) { if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) {
return this.parent.client return this.parent.client
} }
if (this.content.constructor === ContentLink) { if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
const content = /** @type {ContentLink} */ (this.content) const content = /** @type {any} */ (this.content).type
if (content.link.id.client !== this.id.client) { if (content._id.client !== this.id.client) {
return content.link.id.client return content._id.client
} }
} }
@ -540,7 +538,7 @@ export class Item extends AbstractStruct {
addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub) addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
if (this.linkedBy !== null) { if (this.linkedBy !== null) {
for (let link of this.linkedBy) { for (let link of this.linkedBy) {
addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (link.parent), link.parentSub) addChangedTypeToTransaction(transaction, link, this.parentSub)
} }
} }
if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) { if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
@ -647,7 +645,12 @@ export class Item extends AbstractStruct {
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
addChangedTypeToTransaction(transaction, parent, this.parentSub) addChangedTypeToTransaction(transaction, parent, this.parentSub)
this.content.delete(transaction) this.content.delete(transaction)
this.linkedBy = null if (this.linkedBy !== null) {
for (let link of this.linkedBy) {
addChangedTypeToTransaction(transaction, link, this.parentSub)
}
this.linkedBy = null
}
} }
} }
@ -744,8 +747,7 @@ export const contentRefs = [
readContentType, // 7 readContentType, // 7
readContentAny, // 8 readContentAny, // 8
readContentDoc, // 9 readContentDoc, // 9
() => { error.unexpectedCase() }, // 10 - Skip is not ItemContent () => { error.unexpectedCase() } // 10 - Skip is not ItemContent
readContentWeakLink // 11
] ]
/** /**

View File

@ -11,7 +11,7 @@ import {
ContentAny, ContentAny,
ContentBinary, ContentBinary,
getItemCleanStart, getItemCleanStart,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, YWeakLink, ContentLink, // eslint-disable-line ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, YWeakLink, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as map from 'lib0/map' import * as map from 'lib0/map'
@ -309,6 +309,11 @@ export class AbstractType {
this._item = item this._item = item
} }
/**
* @param {Transaction} transaction
*/
_delete (transaction) { }
/** /**
* @return {AbstractType<EventType>} * @return {AbstractType<EventType>}
*/ */
@ -669,10 +674,6 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c))) left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
left.integrate(transaction, 0) left.integrate(transaction, 0)
break break
case YWeakLink:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentLink(/** @type {YWeakLink<any>} */ (c)))
left.integrate(transaction, 0)
break
default: default:
if (c instanceof AbstractType) { if (c instanceof AbstractType) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c)) left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
@ -855,9 +856,6 @@ export const typeMapSet = (transaction, parent, key, value) => {
case Doc: case Doc:
content = new ContentDoc(/** @type {Doc} */ (value)) content = new ContentDoc(/** @type {Doc} */ (value))
break break
case YWeakLink:
content = new ContentLink(/** @type {YWeakLink<any>} */ (value))
break;
default: default:
if (value instanceof AbstractType) { if (value instanceof AbstractType) {
content = new ContentType(value) content = new ContentType(value)

View File

@ -1,4 +1,13 @@
import { AbstractType, GC, ID, Item, Transaction, YEvent } from "yjs" import { decoding, encoding, error } from "lib0"
import {
YEvent, Transaction, ID, GC, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item,
transact,
getItemCleanEnd,
createID,
getItemCleanStart,
callTypeObservers,
YWeakLinkRefID
} from "../internals.js"
/** /**
* @template T extends AbstractType<any> * @template T extends AbstractType<any>
@ -9,27 +18,27 @@ export class YWeakLinkEvent extends YEvent {
/** /**
* @param {YWeakLink<T>} ylink The YWeakLink to which this event was propagated to. * @param {YWeakLink<T>} ylink The YWeakLink to which this event was propagated to.
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {YEvent<any>} source Source event that has been propagated to ylink.
*/ */
constructor (ylink, transaction, source) { constructor (ylink, transaction) {
super(ylink, transaction) super(ylink, transaction)
this.source = source
} }
} }
/** /**
* @template T * @template T
* @extends AbstractType<YWeakLinkEvent<T>>
* *
* Weak link to another value stored somewhere in the document. * Weak link to another value stored somewhere in the document.
*/ */
export class YWeakLink { export class YWeakLink extends AbstractType {
/** /**
* @param {ID} id * @param {ID} id
* @param {Item|GC|null} item * @param {Item|GC|null} item
*/ */
constructor(id, item) { constructor(id, item) {
this.id = id super()
this.item = item this._id = id
this._linkedItem = item
} }
/** /**
@ -38,14 +47,14 @@ export class YWeakLink {
* @return {T|undefined} * @return {T|undefined}
*/ */
deref() { deref() {
if (this.item !== null && this.item.constructor === Item) { if (this._linkedItem !== null && this._linkedItem.constructor === Item) {
let item = this.item let item = this._linkedItem
if (item.parentSub !== null) { if (item.parentSub !== null) {
// for map types go to the most recent one // for map types go to the most recent one
while (item.right !== null) { while (item.right !== null) {
item = item.right item = item.right
} }
this.item = item this._linkedItem = item
} }
if (!item.deleted) { if (!item.deleted) {
return item.content.getContent()[0] return item.content.getContent()[0]
@ -53,4 +62,155 @@ export class YWeakLink {
} }
return undefined; return undefined;
} }
/**
* 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|null} item
*/
_integrate (y, item) {
super._integrate(y, item)
if (item !== null) {
transact(y, (transaction) => {
let sourceItem = this._linkedItem !== null ? this._linkedItem : getItemCleanStart(transaction, this._id)
if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
// for maps, advance to most recent item
while (sourceItem.right !== null) {
sourceItem = sourceItem.right
}
}
if (!sourceItem.deleted && sourceItem.length > 1) {
sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
}
this._linkedItem = sourceItem
if (!sourceItem.deleted) {
const src = /** @type {Item} */ (sourceItem)
if (src.linkedBy === null) {
src.linkedBy = new Set()
}
src.linkedBy.add(this)
}
})
}
}
/**
* @param {Transaction} transaction
*/
_delete (transaction) {
if (this._item !== null && this._linkedItem !== null && !this._linkedItem.deleted) {
const item = /** @type {Item} */ (this._linkedItem)
if (item.linkedBy !== null) {
item.linkedBy.delete(this)
}
this._linkedItem = null
}
}
/**
* @return {YWeakLink<T>}
*/
_copy () {
return new YWeakLink(this._id, this._linkedItem)
}
/**
* @return {YWeakLink<T>}
*/
clone () {
return new YWeakLink(this._id, this._linkedItem)
}
/**
* Creates YWeakLinkEvent 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 YWeakLinkEvent(this, transaction))
}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
*/
_write (encoder) {
encoder.writeTypeRef(YWeakLinkRefID)
const flags = 0 // flags that could be used in the future
encoding.writeUint8(encoder.restEncoder, flags)
encoder.writeLeftID(this._id)
}
} }
/**
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
* @return {YWeakLink<any>}
*/
export const readYWeakLink = decoder => {
const flags = decoding.readUint8(decoder.restDecoder)
const id = decoder.readLeftID()
return new YWeakLink(id, null)
}
const lengthExceeded = error.create('Length exceeded!')
/**
* Returns a {WeakLink} to an YArray element at given index.
*
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {number} index
* @return {YWeakLink<any>}
*/
export const arrayWeakLink = (transaction, parent, index) => {
let item = parent._start
for (; item !== null; item = item.right) {
if (!item.deleted && item.countable) {
if (index < item.length) {
if (index > 0) {
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index))
}
if (item.length > 1) {
item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock))
}
const link = new YWeakLink(item.id, item)
if (item.linkedBy === null) {
item.linkedBy = new Set()
}
item.linkedBy.add(link)
return link
}
index -= item.length
}
}
throw lengthExceeded
}
/**
* Returns a {WeakLink} to an YMap element at given key.
*
* @param {AbstractType<any>} parent
* @param {string} key
* @return {YWeakLink<any>|undefined}
*/
export const mapWeakLink = (parent, key) => {
const item = parent._map.get(key)
if (item !== undefined) {
const link = new YWeakLink(item.id, item)
if (item.linkedBy === null) {
item.linkedBy = new Set()
}
item.linkedBy.add(link)
return link
} else {
return undefined
}
}

View File

@ -118,55 +118,69 @@ export const testDeleteSource = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testObserveMapLinkArrayRemove = tc => { export const testObserveMapUpdate = tc => {
const doc = new Y.Doc() const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
const map = doc.getMap('map') map0.set('a', 'value')
const array = doc.getArray('array') const link0 = /** @type {Y.WeakLink<String>} */ (map0.link('a'))
array.insert(0, [1])
const link = array.link(0)
map.set('key', link)
/** /**
* @type {any} * @type {any}
*/ */
let keys = null let target0
map.observe((e) => { link0.observe((e) => target0 = e.target)
console.log('map received event', e) map0.set('b', link0)
keys = e.keys
})
array.delete(0) testConnector.flushAllMessages()
t.compare(keys.get('key'), { action:'delete', oldValue: 1, newValue: null }) let link1 = /** @type {Y.WeakLink<String>} */ (map1.get('b'))
} t.compare(link1.deref(), 'value')
/**
* @param {t.TestCase} tc
*/
export const testObserveMapLinkMapUpdate = tc => {
const doc = new Y.Doc()
const map1 = doc.getMap('map1')
const map2 = doc.getMap('map2')
/** /**
* @type {Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>} * @type {any}
*/ */
let keys let target1
map1.observe((e) => keys = e.keys) link1.observe((e) => target1 = e.target)
map2.set('key', 'value1') map0.set('a', 'value2')
const link = map2.link('key') t.compare(target0.deref(), 'value2')
map1.set('other-key', link)
keys = /** @type {any} */ (null) testConnector.flushAllMessages()
map2.set('key', 'value2') t.compare(target1.deref(), 'value2')
t.compare(keys.get('key'), { action:'update', oldValue: 'value1', newValue: 'value2' })
} }
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testObserveMapLinkMapRemove = tc => { export const testObserveMapDelete = tc => {
const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
map0.set('a', 'value')
const link0 = /** @type {Y.WeakLink<String>} */ (map0.link('a'))
/**
* @type {any}
*/
let target0
link0.observe((e) => target0 = e.target)
map0.set('b', link0)
testConnector.flushAllMessages()
let link1 = /** @type {Y.WeakLink<String>} */ (map1.get('b'))
t.compare(link1.deref(), 'value')
/**
* @type {any}
*/
let target1
link1.observe((e) => target1 = e.target)
map0.delete('a')
t.compare(target0.deref(), undefined)
testConnector.flushAllMessages()
t.compare(target1.deref(), undefined)
}
/**
* @param {t.TestCase} tc
*/
const testObserveMapLinkMapRemove = tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const map1 = doc.getMap('map1') const map1 = doc.getMap('map1')
const map2 = doc.getMap('map2') const map2 = doc.getMap('map2')
@ -189,7 +203,7 @@ export const testObserveMapLinkMapRemove = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testObserveArrayLinkMapRemove = tc => { const testObserveArrayLinkMapRemove = tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const array = doc.getArray('array') const array = doc.getArray('array')
const map = doc.getMap('map') const map = doc.getMap('map')
@ -212,7 +226,7 @@ export const testObserveArrayLinkMapRemove = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testObserveArrayLinkMapUpdate = tc => { const testObserveArrayLinkMapUpdate = tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const array = doc.getArray('array') const array = doc.getArray('array')
const map = doc.getMap('map') const map = doc.getMap('map')
@ -235,7 +249,7 @@ export const testObserveArrayLinkMapUpdate = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testObserveTransitive = tc => { const testObserveTransitive = tc => {
// test observers in a face of linked chains of values // test observers in a face of linked chains of values
const doc = new Y.Doc() const doc = new Y.Doc()
const map1 = doc.getMap('map1') const map1 = doc.getMap('map1')
@ -262,7 +276,7 @@ export const testObserveTransitive = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testDeepObserveMap = tc => { const testDeepObserveMap = tc => {
// test observers in a face of linked chains of values // test observers in a face of linked chains of values
const doc = new Y.Doc() const doc = new Y.Doc()
const map = doc.getMap('map') const map = doc.getMap('map')
@ -297,7 +311,7 @@ export const testDeepObserveMap = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testDeepObserveArray = tc => { const testDeepObserveArray = tc => {
// test observers in a face of linked chains of values // test observers in a face of linked chains of values
const doc = new Y.Doc() const doc = new Y.Doc()
const map = doc.getMap('map') const map = doc.getMap('map')
@ -332,7 +346,7 @@ export const testDeepObserveArray = tc => {
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testDeepObserveRecursive = tc => { const testDeepObserveRecursive = tc => {
// test observers in a face of linked chains of values // test observers in a face of linked chains of values
const doc = new Y.Doc() const doc = new Y.Doc()
const root = doc.getArray('array') const root = doc.getArray('array')