implement snapshot & event.changes
This commit is contained in:
		
							parent
							
								
									8bcff6138c
								
							
						
					
					
						commit
						bb1c0b809f
					
				@ -38,7 +38,8 @@ export {
 | 
			
		||||
  getState,
 | 
			
		||||
  Snapshot,
 | 
			
		||||
  createSnapshot,
 | 
			
		||||
  createSnapshotFromDoc,
 | 
			
		||||
  snapshot,
 | 
			
		||||
  emptySnapshot,
 | 
			
		||||
  findRootTypeKey,
 | 
			
		||||
  typeListToArraySnapshot,
 | 
			
		||||
  typeMapGetSnapshot,
 | 
			
		||||
@ -46,5 +47,9 @@ export {
 | 
			
		||||
  applyUpdate,
 | 
			
		||||
  encodeStateAsUpdate,
 | 
			
		||||
  encodeStateVector,
 | 
			
		||||
  UndoManager
 | 
			
		||||
  UndoManager,
 | 
			
		||||
  decodeSnapshot,
 | 
			
		||||
  encodeSnapshot,
 | 
			
		||||
  isDeleted,
 | 
			
		||||
  equalSnapshots
 | 
			
		||||
} from './internals.js'
 | 
			
		||||
 | 
			
		||||
@ -584,7 +584,7 @@ export const typeMapHas = (parent, key) => {
 | 
			
		||||
 */
 | 
			
		||||
export const typeMapGetSnapshot = (parent, key, snapshot) => {
 | 
			
		||||
  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
 | 
			
		||||
  }
 | 
			
		||||
  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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const createDeleteSet = () => new DeleteSet()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {StructStore} ss
 | 
			
		||||
 * @return {DeleteSet} Merged and sorted DeleteSet
 | 
			
		||||
@ -177,7 +179,7 @@ export const addToDeleteSet = (ds, id, length) => {
 | 
			
		||||
 * @function
 | 
			
		||||
 */
 | 
			
		||||
export const createDeleteSetFromStructStore = ss => {
 | 
			
		||||
  const ds = new DeleteSet()
 | 
			
		||||
  const ds = createDeleteSet()
 | 
			
		||||
  ss.clients.forEach((structs, client) => {
 | 
			
		||||
    /**
 | 
			
		||||
     * @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 {Transaction} transaction
 | 
			
		||||
@ -232,7 +254,7 @@ export const writeDeleteSet = (encoder, ds) => {
 | 
			
		||||
 * @private
 | 
			
		||||
 * @function
 | 
			
		||||
 */
 | 
			
		||||
export const readDeleteSet = (decoder, transaction, store) => {
 | 
			
		||||
export const readAndApplyDeleteSet = (decoder, transaction, store) => {
 | 
			
		||||
  const unappliedDS = new DeleteSet()
 | 
			
		||||
  const numClients = decoding.readVarUint(decoder)
 | 
			
		||||
  for (let i = 0; i < numClients; i++) {
 | 
			
		||||
@ -279,6 +301,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (unappliedDS.clients.size > 0) {
 | 
			
		||||
    // TODO: no need for encoding+decoding ds anymore
 | 
			
		||||
    const unappliedDSEncoder = encoding.createEncoder()
 | 
			
		||||
    writeDeleteSet(unappliedDSEncoder, unappliedDS)
 | 
			
		||||
    store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
 | 
			
		||||
 | 
			
		||||
@ -6,18 +6,26 @@ import {
 | 
			
		||||
  getItemCleanStart,
 | 
			
		||||
  createID,
 | 
			
		||||
  iterateDeletedStructs,
 | 
			
		||||
  writeDeleteSet,
 | 
			
		||||
  writeStateVector,
 | 
			
		||||
  readDeleteSet,
 | 
			
		||||
  readStateVector,
 | 
			
		||||
  createDeleteSet,
 | 
			
		||||
  getState,
 | 
			
		||||
  Transaction, Doc, DeleteSet, Item // eslint-disable-line
 | 
			
		||||
} from '../internals.js'
 | 
			
		||||
 | 
			
		||||
import * as map from 'lib0/map.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 {
 | 
			
		||||
  /**
 | 
			
		||||
   * @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}
 | 
			
		||||
     * @private
 | 
			
		||||
@ -28,10 +36,64 @@ export class Snapshot {
 | 
			
		||||
     * @type {Map<number,number>}
 | 
			
		||||
     * @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 {Map<number,number>} sm
 | 
			
		||||
@ -39,11 +101,13 @@ export class Snapshot {
 | 
			
		||||
 */
 | 
			
		||||
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
 | 
			
		||||
 | 
			
		||||
export const emptySnapshot = createSnapshot(createDeleteSet(), new Map())
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Doc} doc
 | 
			
		||||
 * @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
 | 
			
		||||
@ -53,7 +117,7 @@ export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromSt
 | 
			
		||||
 * @function
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
  // check if we already split for this snapshot
 | 
			
		||||
  if (!meta.has(snapshot)) {
 | 
			
		||||
    snapshot.sm.forEach((clock, client) => {
 | 
			
		||||
    snapshot.sv.forEach((clock, client) => {
 | 
			
		||||
      if (clock < getState(store, client)) {
 | 
			
		||||
        getItemCleanStart(transaction, store, createID(client, clock))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
 | 
			
		||||
    meta.add(snapshot)
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  isDeleted,
 | 
			
		||||
  AbstractType, Transaction, AbstractStruct // eslint-disable-line
 | 
			
		||||
  Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
 | 
			
		||||
} from '../internals.js'
 | 
			
		||||
 | 
			
		||||
import * as set from 'lib0/set.js'
 | 
			
		||||
import * as array from 'lib0/array.js'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * YEvent describes the changes on a YType.
 | 
			
		||||
 */
 | 
			
		||||
@ -28,6 +31,10 @@ export class YEvent {
 | 
			
		||||
     * @type {Transaction}
 | 
			
		||||
     */
 | 
			
		||||
    this.transaction = transaction
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Object|null}
 | 
			
		||||
     */
 | 
			
		||||
    this._changes = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -65,6 +72,113 @@ export class YEvent {
 | 
			
		||||
  adds (struct) {
 | 
			
		||||
    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,
 | 
			
		||||
  getState,
 | 
			
		||||
  getStateVector,
 | 
			
		||||
  readDeleteSet,
 | 
			
		||||
  readAndApplyDeleteSet,
 | 
			
		||||
  writeDeleteSet,
 | 
			
		||||
  createDeleteSetFromStructStore,
 | 
			
		||||
  Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
 | 
			
		||||
@ -230,7 +230,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => {
 | 
			
		||||
  const pendingReaders = store.pendingDeleteReaders
 | 
			
		||||
  store.pendingDeleteReaders = []
 | 
			
		||||
  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) =>
 | 
			
		||||
  ydoc.transact(transaction => {
 | 
			
		||||
    readStructs(decoder, transaction, ydoc.store)
 | 
			
		||||
    readDeleteSet(decoder, transaction, ydoc.store)
 | 
			
		||||
    readAndApplyDeleteSet(decoder, transaction, ydoc.store)
 | 
			
		||||
  }, transactionOrigin)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -381,6 +381,22 @@ export const readStateVector = decoder => {
 | 
			
		||||
 */
 | 
			
		||||
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`.
 | 
			
		||||
 *
 | 
			
		||||
@ -389,16 +405,7 @@ export const decodeStateVector = decodedState => readStateVector(decoding.create
 | 
			
		||||
 *
 | 
			
		||||
 * @function
 | 
			
		||||
 */
 | 
			
		||||
export const writeDocumentStateVector = (encoder, doc) => {
 | 
			
		||||
  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
 | 
			
		||||
}
 | 
			
		||||
export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store))
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Encode State as Uint8Array.
 | 
			
		||||
 | 
			
		||||
@ -191,6 +191,33 @@ export const testInsertAndDeleteEventsForTypes = tc => {
 | 
			
		||||
  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
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -292,6 +292,54 @@ export const testThrowsAddAndUpdateAndDeleteEvents = tc => {
 | 
			
		||||
  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
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -98,7 +98,7 @@ export const testSnapshot = tc => {
 | 
			
		||||
  text0.applyDelta([{
 | 
			
		||||
    insert: 'abcd'
 | 
			
		||||
  }])
 | 
			
		||||
  const snapshot1 = Y.createSnapshotFromDoc(doc0)
 | 
			
		||||
  const snapshot1 = Y.snapshot(doc0)
 | 
			
		||||
  text0.applyDelta([{
 | 
			
		||||
    retain: 1
 | 
			
		||||
  }, {
 | 
			
		||||
@ -106,7 +106,7 @@ export const testSnapshot = tc => {
 | 
			
		||||
  }, {
 | 
			
		||||
    delete: 1
 | 
			
		||||
  }])
 | 
			
		||||
  const snapshot2 = Y.createSnapshotFromDoc(doc0)
 | 
			
		||||
  const snapshot2 = Y.snapshot(doc0)
 | 
			
		||||
  text0.applyDelta([{
 | 
			
		||||
    retain: 2
 | 
			
		||||
  }, {
 | 
			
		||||
@ -140,7 +140,7 @@ export const testSnapshotDeleteAfter = tc => {
 | 
			
		||||
  text0.applyDelta([{
 | 
			
		||||
    insert: 'abcd'
 | 
			
		||||
  }])
 | 
			
		||||
  const snapshot1 = Y.createSnapshotFromDoc(doc0)
 | 
			
		||||
  const snapshot1 = Y.snapshot(doc0)
 | 
			
		||||
  text0.applyDelta([{
 | 
			
		||||
    retain: 4
 | 
			
		||||
  }, {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user