add tests to snapshots case and fix the case of empty ranges
This commit is contained in:
parent
be8cc8a20c
commit
e1a2ccd7f6
@ -3,7 +3,6 @@ import {
|
|||||||
isDeleted,
|
isDeleted,
|
||||||
createDeleteSetFromStructStore,
|
createDeleteSetFromStructStore,
|
||||||
getStateVector,
|
getStateVector,
|
||||||
getItem,
|
|
||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
iterateDeletedStructs,
|
iterateDeletedStructs,
|
||||||
writeDeleteSet,
|
writeDeleteSet,
|
||||||
@ -12,22 +11,18 @@ import {
|
|||||||
readStateVector,
|
readStateVector,
|
||||||
createDeleteSet,
|
createDeleteSet,
|
||||||
createID,
|
createID,
|
||||||
ID,
|
|
||||||
getState,
|
getState,
|
||||||
findIndexCleanStart,
|
|
||||||
AbstractStruct,
|
|
||||||
writeClientsStructs,
|
|
||||||
findIndexSS,
|
findIndexSS,
|
||||||
readUpdateV2,
|
UpdateEncoderV2,
|
||||||
UpdateEncoderV2, UpdateDecoderV2,
|
DefaultDSEncoder,
|
||||||
AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
|
applyUpdateV2,
|
||||||
|
AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, 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 decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import { DefaultDSEncoder } from './encoding.js'
|
|
||||||
|
|
||||||
export class Snapshot {
|
export class Snapshot {
|
||||||
/**
|
/**
|
||||||
@ -161,59 +156,47 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
|
|||||||
/**
|
/**
|
||||||
* @param {Doc} originDoc
|
* @param {Doc} originDoc
|
||||||
* @param {Snapshot} snapshot
|
* @param {Snapshot} snapshot
|
||||||
|
* @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
|
||||||
* @return {Doc}
|
* @return {Doc}
|
||||||
*/
|
*/
|
||||||
export const createDocFromSnapshot = (originDoc, snapshot) => {
|
export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
|
||||||
if (originDoc.gc) {
|
if (originDoc.gc) {
|
||||||
// we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
|
// we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
|
||||||
throw new Error('originDoc must not be garbage collected')
|
throw new Error('originDoc must not be garbage collected')
|
||||||
}
|
}
|
||||||
const { sv, ds } = snapshot
|
const { sv, ds } = snapshot
|
||||||
const needState = new Map(sv)
|
|
||||||
|
|
||||||
let len = 0
|
const encoder = new UpdateEncoderV2()
|
||||||
const tempStructs = []
|
|
||||||
/**
|
|
||||||
* State Map
|
|
||||||
* @type any[]
|
|
||||||
*/
|
|
||||||
const itemsToIntegrate = []
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type Uint8Array
|
|
||||||
*/
|
|
||||||
let updateBuffer = new Uint8Array()
|
|
||||||
originDoc.transact(transaction => {
|
originDoc.transact(transaction => {
|
||||||
const encoder = new UpdateEncoderV2()
|
let size = 0
|
||||||
|
sv.forEach(clock => {
|
||||||
encoding.writeVarUint(encoder.restEncoder, sv.size)
|
if (clock > 0) {
|
||||||
|
size++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
encoding.writeVarUint(encoder.restEncoder, size)
|
||||||
// splitting the structs before writing them to the encoder
|
// splitting the structs before writing them to the encoder
|
||||||
for (const [client, clock] of sv) {
|
for (const [client, clock] of sv) {
|
||||||
|
if (clock === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (clock < getState(originDoc.store, client)) {
|
if (clock < getState(originDoc.store, client)) {
|
||||||
getItemCleanStart(transaction, createID(client, clock))
|
getItemCleanStart(transaction, createID(client, clock))
|
||||||
}
|
}
|
||||||
|
|
||||||
const structs = originDoc.store.clients.get(client) || []
|
const structs = originDoc.store.clients.get(client) || []
|
||||||
const lastStructIndex = findIndexSS(structs, clock - 1)
|
const lastStructIndex = findIndexSS(structs, clock - 1)
|
||||||
// write # encoded structs
|
// write # encoded structs
|
||||||
encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
|
encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
|
||||||
encoder.writeClient(client)
|
encoder.writeClient(client)
|
||||||
encoding.writeVarUint(encoder.restEncoder, structs[0].id.clock)
|
// first clock written is 0
|
||||||
const firstStruct = structs[0]
|
encoding.writeVarUint(encoder.restEncoder, 0)
|
||||||
firstStruct.write(encoder, 0)
|
for (let i = 0; i <= lastStructIndex; i++) {
|
||||||
for (let i = 1; i <= lastStructIndex; i++) {
|
|
||||||
structs[i].write(encoder, 0)
|
structs[i].write(encoder, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDeleteSet(encoder, ds)
|
writeDeleteSet(encoder, ds)
|
||||||
|
|
||||||
updateBuffer = encoder.toUint8Array()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const newDoc = new Doc()
|
applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
|
||||||
|
|
||||||
readUpdateV2(decoding.createDecoder(updateBuffer), newDoc, 'snapshot')
|
|
||||||
|
|
||||||
return newDoc
|
return newDoc
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,26 @@ export const testBasicRestoreSnapshot = tc => {
|
|||||||
t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
|
t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testEmptyRestoreSnapshot = tc => {
|
||||||
|
const doc = new Doc({ gc: false })
|
||||||
|
const snap = snapshot(doc)
|
||||||
|
snap.sv.set(9999, 0)
|
||||||
|
doc.getArray().insert(0, ['world'])
|
||||||
|
|
||||||
|
const docRestored = createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
|
t.compare(docRestored.getArray().toArray(), [])
|
||||||
|
t.compare(doc.getArray().toArray(), ['world'])
|
||||||
|
|
||||||
|
// now this snapshot reflects the latest state. It shoult still work.
|
||||||
|
const snap2 = snapshot(doc)
|
||||||
|
const docRestored2 = createDocFromSnapshot(doc, snap2)
|
||||||
|
t.compare(docRestored2.getArray().toArray(), ['world'])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@ -74,7 +94,6 @@ export const testRestoreLeftItem = tc => {
|
|||||||
t.compare(doc.getArray('array').toArray(), ['item0'])
|
t.compare(doc.getArray('array').toArray(), ['item0'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@ -91,7 +110,6 @@ export const testDeletedItemsBase = tc => {
|
|||||||
t.compare(doc.getArray('array').toArray(), ['item0'])
|
t.compare(doc.getArray('array').toArray(), ['item0'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@ -108,7 +126,6 @@ export const testDeletedItems2 = tc => {
|
|||||||
t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
|
t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@ -152,4 +169,3 @@ export const testDependentChanges = tc => {
|
|||||||
const docRestored1 = createDocFromSnapshot(array1.doc, snap)
|
const docRestored1 = createDocFromSnapshot(array1.doc, snap)
|
||||||
t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user