implement snapshot & event.changes
This commit is contained in:
parent
8bcff6138c
commit
bb1c0b809f
@ -38,7 +38,8 @@ export {
|
|||||||
getState,
|
getState,
|
||||||
Snapshot,
|
Snapshot,
|
||||||
createSnapshot,
|
createSnapshot,
|
||||||
createSnapshotFromDoc,
|
snapshot,
|
||||||
|
emptySnapshot,
|
||||||
findRootTypeKey,
|
findRootTypeKey,
|
||||||
typeListToArraySnapshot,
|
typeListToArraySnapshot,
|
||||||
typeMapGetSnapshot,
|
typeMapGetSnapshot,
|
||||||
@ -46,5 +47,9 @@ export {
|
|||||||
applyUpdate,
|
applyUpdate,
|
||||||
encodeStateAsUpdate,
|
encodeStateAsUpdate,
|
||||||
encodeStateVector,
|
encodeStateVector,
|
||||||
UndoManager
|
UndoManager,
|
||||||
|
decodeSnapshot,
|
||||||
|
encodeSnapshot,
|
||||||
|
isDeleted,
|
||||||
|
equalSnapshots
|
||||||
} from './internals.js'
|
} from './internals.js'
|
||||||
|
@ -584,7 +584,7 @@ export const typeMapHas = (parent, key) => {
|
|||||||
*/
|
*/
|
||||||
export const typeMapGetSnapshot = (parent, key, snapshot) => {
|
export const typeMapGetSnapshot = (parent, key, snapshot) => {
|
||||||
let v = parent._map.get(key) || null
|
let v = parent._map.get(key) || null
|
||||||
while (v !== null && (!snapshot.sm.has(v.id.client) || v.id.clock >= (snapshot.sm.get(v.id.client) || 0))) {
|
while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
|
||||||
v = v.left
|
v = v.left
|
||||||
}
|
}
|
||||||
return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
|
return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
|
||||||
|
@ -169,6 +169,8 @@ export const addToDeleteSet = (ds, id, length) => {
|
|||||||
map.setIfUndefined(ds.clients, id.client, () => []).push(new DeleteItem(id.clock, length))
|
map.setIfUndefined(ds.clients, id.client, () => []).push(new DeleteItem(id.clock, length))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createDeleteSet = () => new DeleteSet()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StructStore} ss
|
* @param {StructStore} ss
|
||||||
* @return {DeleteSet} Merged and sorted DeleteSet
|
* @return {DeleteSet} Merged and sorted DeleteSet
|
||||||
@ -177,7 +179,7 @@ export const addToDeleteSet = (ds, id, length) => {
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const createDeleteSetFromStructStore = ss => {
|
export const createDeleteSetFromStructStore = ss => {
|
||||||
const ds = new DeleteSet()
|
const ds = createDeleteSet()
|
||||||
ss.clients.forEach((structs, client) => {
|
ss.clients.forEach((structs, client) => {
|
||||||
/**
|
/**
|
||||||
* @type {Array<DeleteItem>}
|
* @type {Array<DeleteItem>}
|
||||||
@ -224,6 +226,26 @@ export const writeDeleteSet = (encoder, ds) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {decoding.Decoder} decoder
|
||||||
|
* @return {DeleteSet}
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
export const readDeleteSet = decoder => {
|
||||||
|
const ds = new DeleteSet()
|
||||||
|
const numClients = decoding.readVarUint(decoder)
|
||||||
|
for (let i = 0; i < numClients; i++) {
|
||||||
|
const client = decoding.readVarUint(decoder)
|
||||||
|
const numberOfDeletes = decoding.readVarUint(decoder)
|
||||||
|
for (let i = 0; i < numberOfDeletes; i++) {
|
||||||
|
addToDeleteSet(ds, createID(client, decoding.readVarUint(decoder)), decoding.readVarUint(decoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
@ -232,7 +254,7 @@ export const writeDeleteSet = (encoder, ds) => {
|
|||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const readDeleteSet = (decoder, transaction, store) => {
|
export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
||||||
const unappliedDS = new DeleteSet()
|
const unappliedDS = new DeleteSet()
|
||||||
const numClients = decoding.readVarUint(decoder)
|
const numClients = decoding.readVarUint(decoder)
|
||||||
for (let i = 0; i < numClients; i++) {
|
for (let i = 0; i < numClients; i++) {
|
||||||
@ -279,6 +301,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unappliedDS.clients.size > 0) {
|
if (unappliedDS.clients.size > 0) {
|
||||||
|
// TODO: no need for encoding+decoding ds anymore
|
||||||
const unappliedDSEncoder = encoding.createEncoder()
|
const unappliedDSEncoder = encoding.createEncoder()
|
||||||
writeDeleteSet(unappliedDSEncoder, unappliedDS)
|
writeDeleteSet(unappliedDSEncoder, unappliedDS)
|
||||||
store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
|
store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
|
||||||
|
@ -6,18 +6,26 @@ import {
|
|||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
createID,
|
createID,
|
||||||
iterateDeletedStructs,
|
iterateDeletedStructs,
|
||||||
|
writeDeleteSet,
|
||||||
|
writeStateVector,
|
||||||
|
readDeleteSet,
|
||||||
|
readStateVector,
|
||||||
|
createDeleteSet,
|
||||||
|
getState,
|
||||||
Transaction, Doc, DeleteSet, Item // eslint-disable-line
|
Transaction, Doc, DeleteSet, Item // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as map from 'lib0/map.js'
|
import * as map from 'lib0/map.js'
|
||||||
import * as set from 'lib0/set.js'
|
import * as set from 'lib0/set.js'
|
||||||
|
import * as encoding from 'lib0/encoding.js'
|
||||||
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
export class Snapshot {
|
export class Snapshot {
|
||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} ds
|
||||||
* @param {Map<number,number>} sm state map
|
* @param {Map<number,number>} sv state map
|
||||||
*/
|
*/
|
||||||
constructor (ds, sm) {
|
constructor (ds, sv) {
|
||||||
/**
|
/**
|
||||||
* @type {DeleteSet}
|
* @type {DeleteSet}
|
||||||
* @private
|
* @private
|
||||||
@ -28,10 +36,64 @@ export class Snapshot {
|
|||||||
* @type {Map<number,number>}
|
* @type {Map<number,number>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.sm = sm
|
this.sv = sv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Snapshot} snap1
|
||||||
|
* @param {Snapshot} snap2
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const equalSnapshots = (snap1, snap2) => {
|
||||||
|
const ds1 = snap1.ds.clients
|
||||||
|
const ds2 = snap2.ds.clients
|
||||||
|
const sv1 = snap1.sv
|
||||||
|
const sv2 = snap2.sv
|
||||||
|
if (sv1.size !== sv2.size || ds1.size !== ds2.size) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (const [key, value] of sv1) {
|
||||||
|
if (sv2.get(key) !== value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [client, dsitems1] of ds1) {
|
||||||
|
const dsitems2 = ds2.get(client) || []
|
||||||
|
if (dsitems1.length !== dsitems2.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let i = 0; i < dsitems1.length; i++) {
|
||||||
|
const dsitem1 = dsitems1[i]
|
||||||
|
const dsitem2 = dsitems2[i]
|
||||||
|
if (dsitem1.clock !== dsitem2.clock || dsitem1.len !== dsitem2.len) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Snapshot} snapshot
|
||||||
|
* @return {Uint8Array}
|
||||||
|
*/
|
||||||
|
export const encodeSnapshot = snapshot => {
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
writeDeleteSet(encoder, snapshot.ds)
|
||||||
|
writeStateVector(encoder, snapshot.sv)
|
||||||
|
return encoding.toUint8Array(encoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} buf
|
||||||
|
* @return {Snapshot}
|
||||||
|
*/
|
||||||
|
export const decodeSnapshot = buf => {
|
||||||
|
const decoder = decoding.createDecoder(buf)
|
||||||
|
return new Snapshot(readDeleteSet(decoder), readStateVector(decoder))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} ds
|
||||||
* @param {Map<number,number>} sm
|
* @param {Map<number,number>} sm
|
||||||
@ -39,11 +101,13 @@ export class Snapshot {
|
|||||||
*/
|
*/
|
||||||
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
||||||
|
|
||||||
|
export const emptySnapshot = createSnapshot(createDeleteSet(), new Map())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Doc} doc
|
* @param {Doc} doc
|
||||||
* @return {Snapshot}
|
* @return {Snapshot}
|
||||||
*/
|
*/
|
||||||
export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
|
export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
@ -53,7 +117,7 @@ export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromSt
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
|
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
|
||||||
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,8 +129,10 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
|
|||||||
const store = transaction.doc.store
|
const store = transaction.doc.store
|
||||||
// check if we already split for this snapshot
|
// check if we already split for this snapshot
|
||||||
if (!meta.has(snapshot)) {
|
if (!meta.has(snapshot)) {
|
||||||
snapshot.sm.forEach((clock, client) => {
|
snapshot.sv.forEach((clock, client) => {
|
||||||
getItemCleanStart(transaction, store, createID(client, clock))
|
if (clock < getState(store, client)) {
|
||||||
|
getItemCleanStart(transaction, store, createID(client, clock))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
|
iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
|
||||||
meta.add(snapshot)
|
meta.add(snapshot)
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
isDeleted,
|
isDeleted,
|
||||||
AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
|
import * as set from 'lib0/set.js'
|
||||||
|
import * as array from 'lib0/array.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YEvent describes the changes on a YType.
|
* YEvent describes the changes on a YType.
|
||||||
*/
|
*/
|
||||||
@ -28,6 +31,10 @@ export class YEvent {
|
|||||||
* @type {Transaction}
|
* @type {Transaction}
|
||||||
*/
|
*/
|
||||||
this.transaction = transaction
|
this.transaction = transaction
|
||||||
|
/**
|
||||||
|
* @type {Object|null}
|
||||||
|
*/
|
||||||
|
this._changes = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,6 +72,113 @@ export class YEvent {
|
|||||||
adds (struct) {
|
adds (struct) {
|
||||||
return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
|
return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {{added:Set<Item>,deleted:Set<Item>,delta:Array<{insert:Array<any>}|{delete:number}|{retain:number}>}}
|
||||||
|
*/
|
||||||
|
get changes () {
|
||||||
|
let changes = this._changes
|
||||||
|
if (changes === null) {
|
||||||
|
const target = this.target
|
||||||
|
const added = set.create()
|
||||||
|
const deleted = set.create()
|
||||||
|
/**
|
||||||
|
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
||||||
|
*/
|
||||||
|
const delta = []
|
||||||
|
/**
|
||||||
|
* @type {Map<string,{ action: 'add' | 'update' | 'delete', oldValue: any}>}
|
||||||
|
*/
|
||||||
|
const keys = new Map()
|
||||||
|
changes = {
|
||||||
|
added, deleted, delta, keys
|
||||||
|
}
|
||||||
|
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||||
|
if (changed.has(null)) {
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let lastOp = null
|
||||||
|
const packOp = () => {
|
||||||
|
if (lastOp) {
|
||||||
|
delta.push(lastOp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let item = target._start; item !== null; item = item.right) {
|
||||||
|
if (item.deleted) {
|
||||||
|
if (this.deletes(item)) {
|
||||||
|
if (lastOp === null || lastOp.delete === undefined) {
|
||||||
|
packOp()
|
||||||
|
lastOp = { delete: 0 }
|
||||||
|
}
|
||||||
|
lastOp.delete += item.length
|
||||||
|
deleted.add(item)
|
||||||
|
} // else nop
|
||||||
|
} else {
|
||||||
|
if (this.adds(item)) {
|
||||||
|
if (lastOp === null || lastOp.insert === undefined) {
|
||||||
|
packOp()
|
||||||
|
lastOp = { insert: [] }
|
||||||
|
}
|
||||||
|
lastOp.insert = lastOp.insert.concat(item.content.getContent())
|
||||||
|
added.add(item)
|
||||||
|
} else {
|
||||||
|
if (lastOp === null || lastOp.retain === undefined) {
|
||||||
|
packOp()
|
||||||
|
lastOp = { retain: 0 }
|
||||||
|
}
|
||||||
|
lastOp.retain += item.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastOp !== null && lastOp.retain === undefined) {
|
||||||
|
packOp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed.forEach(key => {
|
||||||
|
if (key !== null) {
|
||||||
|
const item = /** @type {Item} */ (target._map.get(key))
|
||||||
|
/**
|
||||||
|
* @type {'delete' | 'add' | 'update'}
|
||||||
|
*/
|
||||||
|
let action
|
||||||
|
let oldValue
|
||||||
|
if (this.adds(item)) {
|
||||||
|
let prev = item.left
|
||||||
|
while (prev !== null && this.adds(prev)) {
|
||||||
|
prev = prev.left
|
||||||
|
}
|
||||||
|
if (this.deletes(item)) {
|
||||||
|
if (prev !== null && this.deletes(prev)) {
|
||||||
|
action = 'delete'
|
||||||
|
oldValue = array.last(prev.content.getContent())
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (prev !== null && this.deletes(prev)) {
|
||||||
|
action = 'update'
|
||||||
|
oldValue = array.last(prev.content.getContent())
|
||||||
|
} else {
|
||||||
|
action = 'add'
|
||||||
|
oldValue = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.deletes(item)) {
|
||||||
|
action = 'delete'
|
||||||
|
oldValue = array.last(/** @type {Item} */ item.content.getContent())
|
||||||
|
} else {
|
||||||
|
return // nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys.set(key, { action, oldValue })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this._changes = changes
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
readID,
|
readID,
|
||||||
getState,
|
getState,
|
||||||
getStateVector,
|
getStateVector,
|
||||||
readDeleteSet,
|
readAndApplyDeleteSet,
|
||||||
writeDeleteSet,
|
writeDeleteSet,
|
||||||
createDeleteSetFromStructStore,
|
createDeleteSetFromStructStore,
|
||||||
Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
|
Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
|
||||||
@ -230,7 +230,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => {
|
|||||||
const pendingReaders = store.pendingDeleteReaders
|
const pendingReaders = store.pendingDeleteReaders
|
||||||
store.pendingDeleteReaders = []
|
store.pendingDeleteReaders = []
|
||||||
for (let i = 0; i < pendingReaders.length; i++) {
|
for (let i = 0; i < pendingReaders.length; i++) {
|
||||||
readDeleteSet(pendingReaders[i], transaction, store)
|
readAndApplyDeleteSet(pendingReaders[i], transaction, store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +301,7 @@ export const readStructs = (decoder, transaction, store) => {
|
|||||||
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
|
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
|
||||||
ydoc.transact(transaction => {
|
ydoc.transact(transaction => {
|
||||||
readStructs(decoder, transaction, ydoc.store)
|
readStructs(decoder, transaction, ydoc.store)
|
||||||
readDeleteSet(decoder, transaction, ydoc.store)
|
readAndApplyDeleteSet(decoder, transaction, ydoc.store)
|
||||||
}, transactionOrigin)
|
}, transactionOrigin)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,6 +381,22 @@ export const readStateVector = decoder => {
|
|||||||
*/
|
*/
|
||||||
export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState))
|
export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write State Vector to `lib0/encoding.js#Encoder`.
|
||||||
|
*
|
||||||
|
* @param {encoding.Encoder} encoder
|
||||||
|
* @param {Map<number,number>} sv
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
export const writeStateVector = (encoder, sv) => {
|
||||||
|
encoding.writeVarUint(encoder, sv.size)
|
||||||
|
sv.forEach((clock, client) => {
|
||||||
|
encoding.writeVarUint(encoder, client)
|
||||||
|
encoding.writeVarUint(encoder, clock)
|
||||||
|
})
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write State Vector to `lib0/encoding.js#Encoder`.
|
* Write State Vector to `lib0/encoding.js#Encoder`.
|
||||||
*
|
*
|
||||||
@ -389,16 +405,7 @@ export const decodeStateVector = decodedState => readStateVector(decoding.create
|
|||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const writeDocumentStateVector = (encoder, doc) => {
|
export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store))
|
||||||
encoding.writeVarUint(encoder, doc.store.clients.size)
|
|
||||||
doc.store.clients.forEach((structs, client) => {
|
|
||||||
const struct = structs[structs.length - 1]
|
|
||||||
const id = struct.id
|
|
||||||
encoding.writeVarUint(encoder, id.client)
|
|
||||||
encoding.writeVarUint(encoder, id.clock + struct.length)
|
|
||||||
})
|
|
||||||
return encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode State as Uint8Array.
|
* Encode State as Uint8Array.
|
||||||
|
@ -191,6 +191,33 @@ export const testInsertAndDeleteEventsForTypes = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testChangeEvent = tc => {
|
||||||
|
const { array0, users } = init(tc, { users: 2 })
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let changes = null
|
||||||
|
array0.observe(e => {
|
||||||
|
changes = e.changes
|
||||||
|
})
|
||||||
|
const newArr = new Y.Array()
|
||||||
|
array0.insert(0, [newArr, 4, 'dtrn'])
|
||||||
|
t.assert(changes !== null && changes.added.size === 2 && changes.deleted.size === 0)
|
||||||
|
t.compare(changes.delta, [{insert: [newArr, 4, 'dtrn']}])
|
||||||
|
changes = null
|
||||||
|
array0.delete(0, 2)
|
||||||
|
t.assert(changes !== null && changes.added.size === 0 && changes.deleted.size === 2)
|
||||||
|
t.compare(changes.delta, [{ delete: 2 }])
|
||||||
|
changes = null
|
||||||
|
array0.insert(1, [0.1])
|
||||||
|
t.assert(changes !== null && changes.added.size === 1 && changes.deleted.size === 0)
|
||||||
|
t.compare(changes.delta, [{ retain: 1 }, { insert: [0.1] }])
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
@ -292,6 +292,54 @@ export const testThrowsAddAndUpdateAndDeleteEvents = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testChangeEvent = tc => {
|
||||||
|
const { map0, users } = init(tc, { users: 2 })
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let changes = null
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let keyChange = null
|
||||||
|
map0.observe(e => {
|
||||||
|
changes = e.changes
|
||||||
|
})
|
||||||
|
map0.set('a', 1)
|
||||||
|
keyChange = changes.keys.get('a')
|
||||||
|
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
|
||||||
|
map0.set('a', 2)
|
||||||
|
keyChange = changes.keys.get('a')
|
||||||
|
t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 1)
|
||||||
|
users[0].transact(() => {
|
||||||
|
map0.set('a', 3)
|
||||||
|
map0.set('a', 4)
|
||||||
|
})
|
||||||
|
keyChange = changes.keys.get('a')
|
||||||
|
t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 2)
|
||||||
|
users[0].transact(() => {
|
||||||
|
map0.set('b', 1)
|
||||||
|
map0.set('b', 2)
|
||||||
|
})
|
||||||
|
keyChange = changes.keys.get('b')
|
||||||
|
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
|
||||||
|
users[0].transact(() => {
|
||||||
|
map0.set('c', 1)
|
||||||
|
map0.delete('c')
|
||||||
|
})
|
||||||
|
t.assert(changes !== null && changes.keys.size === 0)
|
||||||
|
users[0].transact(() => {
|
||||||
|
map0.set('d', 1)
|
||||||
|
map0.set('d', 2)
|
||||||
|
})
|
||||||
|
keyChange = changes.keys.get('d')
|
||||||
|
t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
@ -98,7 +98,7 @@ export const testSnapshot = tc => {
|
|||||||
text0.applyDelta([{
|
text0.applyDelta([{
|
||||||
insert: 'abcd'
|
insert: 'abcd'
|
||||||
}])
|
}])
|
||||||
const snapshot1 = Y.createSnapshotFromDoc(doc0)
|
const snapshot1 = Y.snapshot(doc0)
|
||||||
text0.applyDelta([{
|
text0.applyDelta([{
|
||||||
retain: 1
|
retain: 1
|
||||||
}, {
|
}, {
|
||||||
@ -106,7 +106,7 @@ export const testSnapshot = tc => {
|
|||||||
}, {
|
}, {
|
||||||
delete: 1
|
delete: 1
|
||||||
}])
|
}])
|
||||||
const snapshot2 = Y.createSnapshotFromDoc(doc0)
|
const snapshot2 = Y.snapshot(doc0)
|
||||||
text0.applyDelta([{
|
text0.applyDelta([{
|
||||||
retain: 2
|
retain: 2
|
||||||
}, {
|
}, {
|
||||||
@ -140,7 +140,7 @@ export const testSnapshotDeleteAfter = tc => {
|
|||||||
text0.applyDelta([{
|
text0.applyDelta([{
|
||||||
insert: 'abcd'
|
insert: 'abcd'
|
||||||
}])
|
}])
|
||||||
const snapshot1 = Y.createSnapshotFromDoc(doc0)
|
const snapshot1 = Y.snapshot(doc0)
|
||||||
text0.applyDelta([{
|
text0.applyDelta([{
|
||||||
retain: 4
|
retain: 4
|
||||||
}, {
|
}, {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user