fix some tests, implement event classes for types, and re-implement logging
This commit is contained in:
parent
755c9eb16e
commit
c92f987496
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
|||||||
import multiEntry from 'rollup-plugin-multi-entry'
|
import multiEntry from 'rollup-plugin-multi-entry'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: 'test/y-xml.tests.js',
|
entry: 'test/y-array.tests.js',
|
||||||
moduleName: 'y-tests',
|
moduleName: 'y-tests',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -3,7 +3,7 @@ import BinaryDecoder from './Binary/Decoder.js'
|
|||||||
|
|
||||||
import { sendSyncStep1, readSyncStep1 } from './MessageHandler/syncStep1.js'
|
import { sendSyncStep1, readSyncStep1 } from './MessageHandler/syncStep1.js'
|
||||||
import { readSyncStep2 } from './MessageHandler/syncStep2.js'
|
import { readSyncStep2 } from './MessageHandler/syncStep2.js'
|
||||||
import { readUpdate } from './MessageHandler/update.js'
|
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
|
||||||
|
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
|
|
||||||
@ -136,19 +136,21 @@ export default class AbstractConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send (uid, buffer) {
|
send (uid, buffer) {
|
||||||
|
const y = this.y
|
||||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
||||||
}
|
}
|
||||||
this.log('%s: Send \'%y\' to %s', this.y.userID, buffer, uid)
|
this.log('%s: Send \'%y\' to %s', y.userID, buffer, uid)
|
||||||
this.logMessage('Message: %Y', buffer)
|
this.logMessage('Message: %Y', [y, buffer])
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast (buffer) {
|
broadcast (buffer) {
|
||||||
|
const y = this.y
|
||||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
||||||
}
|
}
|
||||||
this.log('%s: Broadcast \'%y\'', this.y.userID, buffer)
|
this.log('%s: Broadcast \'%y\'', y.userID, buffer)
|
||||||
this.logMessage('Message: %Y', buffer)
|
this.logMessage('Message: %Y', [y, buffer])
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -177,7 +179,7 @@ export default class AbstractConnector {
|
|||||||
this.broadcast(this.broadcastBuffer.createBuffer())
|
this.broadcast(this.broadcastBuffer.createBuffer())
|
||||||
this.broadcastBuffer = new BinaryEncoder()
|
this.broadcastBuffer = new BinaryEncoder()
|
||||||
}
|
}
|
||||||
})
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,11 +201,13 @@ export default class AbstractConnector {
|
|||||||
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
||||||
*/
|
*/
|
||||||
receiveMessage (sender, buffer, skipAuth) {
|
receiveMessage (sender, buffer, skipAuth) {
|
||||||
|
const y = this.y
|
||||||
|
const userID = y.userID
|
||||||
skipAuth = skipAuth || false
|
skipAuth = skipAuth || false
|
||||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||||
return Promise.reject(new Error('Expected Message to be an ArrayBuffer or Uint8Array!'))
|
return Promise.reject(new Error('Expected Message to be an ArrayBuffer or Uint8Array!'))
|
||||||
}
|
}
|
||||||
if (sender === this.y.userID) {
|
if (sender === userID) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
let decoder = new BinaryDecoder(buffer)
|
let decoder = new BinaryDecoder(buffer)
|
||||||
@ -212,8 +216,8 @@ export default class AbstractConnector {
|
|||||||
encoder.writeVarString(roomname)
|
encoder.writeVarString(roomname)
|
||||||
let messageType = decoder.readVarString()
|
let messageType = decoder.readVarString()
|
||||||
let senderConn = this.connections.get(sender)
|
let senderConn = this.connections.get(sender)
|
||||||
this.log('%s: Receive \'%s\' from %s', this.y.userID, messageType, sender)
|
this.log('%s: Receive \'%s\' from %s', userID, messageType, sender)
|
||||||
this.logMessage('Message: %Y', buffer)
|
this.logMessage('Message: %Y', [y, buffer])
|
||||||
if (senderConn == null && !skipAuth) {
|
if (senderConn == null && !skipAuth) {
|
||||||
throw new Error('Received message from unknown peer!')
|
throw new Error('Received message from unknown peer!')
|
||||||
}
|
}
|
||||||
@ -222,10 +226,10 @@ export default class AbstractConnector {
|
|||||||
if (senderConn.auth == null) {
|
if (senderConn.auth == null) {
|
||||||
senderConn.processAfterAuth.push([messageType, senderConn, decoder, encoder, sender])
|
senderConn.processAfterAuth.push([messageType, senderConn, decoder, encoder, sender])
|
||||||
// check auth
|
// check auth
|
||||||
return this.checkAuth(auth, this.y, sender).then(authPermissions => {
|
return this.checkAuth(auth, y, sender).then(authPermissions => {
|
||||||
if (senderConn.auth == null) {
|
if (senderConn.auth == null) {
|
||||||
senderConn.auth = authPermissions
|
senderConn.auth = authPermissions
|
||||||
this.y.emit('userAuthenticated', {
|
y.emit('userAuthenticated', {
|
||||||
user: senderConn.uid,
|
user: senderConn.uid,
|
||||||
auth: authPermissions
|
auth: authPermissions
|
||||||
})
|
})
|
||||||
@ -250,16 +254,17 @@ export default class AbstractConnector {
|
|||||||
if (messageType === 'sync step 1' && (senderConn.auth === 'write' || senderConn.auth === 'read')) {
|
if (messageType === 'sync step 1' && (senderConn.auth === 'write' || senderConn.auth === 'read')) {
|
||||||
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
|
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
|
||||||
readSyncStep1(decoder, encoder, this.y, senderConn, sender)
|
readSyncStep1(decoder, encoder, this.y, senderConn, sender)
|
||||||
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
|
||||||
this.y.transact(() => {
|
|
||||||
readSyncStep2(decoder, encoder, this.y, senderConn, sender)
|
|
||||||
})
|
|
||||||
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
|
||||||
this.y.transact(() => {
|
|
||||||
readUpdate(decoder, encoder, this.y, senderConn, sender)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unable to receive message')
|
const y = this.y
|
||||||
|
y.transact(function () {
|
||||||
|
if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
||||||
|
readSyncStep2(decoder, encoder, y, senderConn, sender)
|
||||||
|
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
||||||
|
integrateRemoteStructs(decoder, encoder, y, senderConn, sender)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unable to receive message')
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +28,7 @@ function _integrateRemoteStructHelper (y, struct) {
|
|||||||
missingDef.missing--
|
missingDef.missing--
|
||||||
if (missingDef.missing === 0) {
|
if (missingDef.missing === 0) {
|
||||||
let missing = missingDef.struct._fromBinary(y, missingDef.decoder)
|
let missing = missingDef.struct._fromBinary(y, missingDef.decoder)
|
||||||
if (missing.length > 0) {
|
if (missing.length === 0) {
|
||||||
console.error('Missing should be empty!')
|
|
||||||
} else {
|
|
||||||
y._readyToIntegrate.push(missingDef.struct)
|
y._readyToIntegrate.push(missingDef.struct)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,6 +40,21 @@ function _integrateRemoteStructHelper (y, struct) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stringifyStructs (y, decoder, strBuilder) {
|
||||||
|
while (decoder.length !== decoder.pos) {
|
||||||
|
let reference = decoder.readVarUint()
|
||||||
|
let Constr = getStruct(reference)
|
||||||
|
let struct = new Constr()
|
||||||
|
let missing = struct._fromBinary(y, decoder)
|
||||||
|
let logMessage = struct._logString()
|
||||||
|
if (missing.length > 0) {
|
||||||
|
logMessage += missing.map(id => `ID (user: ${id.user}, clock: ${id.clock})`).join(', ')
|
||||||
|
}
|
||||||
|
logMessage += '\n'
|
||||||
|
strBuilder.push(logMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function integrateRemoteStructs (decoder, encoder, y) {
|
export function integrateRemoteStructs (decoder, encoder, y) {
|
||||||
while (decoder.length !== decoder.pos) {
|
while (decoder.length !== decoder.pos) {
|
||||||
let decoderPos = decoder.pos
|
let decoderPos = decoder.pos
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import BinaryDecoder from '../Binary/Decoder.js'
|
import BinaryDecoder from '../Binary/Decoder.js'
|
||||||
import { stringifyUpdate } from './update.js'
|
import { stringifyStructs } from './integrateRemoteStructs.js'
|
||||||
import { stringifySyncStep1 } from './syncStep1.js'
|
import { stringifySyncStep1 } from './syncStep1.js'
|
||||||
import { stringifySyncStep2 } from './syncStep2.js'
|
import { stringifySyncStep2 } from './syncStep2.js'
|
||||||
|
|
||||||
export function messageToString (y, buffer) {
|
export function messageToString ([y, buffer]) {
|
||||||
let decoder = new BinaryDecoder(buffer)
|
let decoder = new BinaryDecoder(buffer)
|
||||||
decoder.readVarString() // read roomname
|
decoder.readVarString() // read roomname
|
||||||
let type = decoder.readVarString()
|
let type = decoder.readVarString()
|
||||||
let strBuilder = []
|
let strBuilder = []
|
||||||
strBuilder.push('\n === ' + type + ' ===\n')
|
strBuilder.push('\n === ' + type + ' ===\n')
|
||||||
if (type === 'update') {
|
if (type === 'update') {
|
||||||
stringifyUpdate(y, decoder, strBuilder)
|
stringifyStructs(y, decoder, strBuilder)
|
||||||
} else if (type === 'sync step 1') {
|
} else if (type === 'sync step 1') {
|
||||||
stringifySyncStep1(y, decoder, strBuilder)
|
stringifySyncStep1(y, decoder, strBuilder)
|
||||||
} else if (type === 'sync step 2') {
|
} else if (type === 'sync step 2') {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { integrateRemoteStructs } from './integrateRemoteStructs.js'
|
import { stringifyStructs, integrateRemoteStructs } from './integrateRemoteStructs.js'
|
||||||
import { stringifyUpdate } from './update.js'
|
|
||||||
import { readDeleteSet } from './deleteSet.js'
|
import { readDeleteSet } from './deleteSet.js'
|
||||||
|
|
||||||
export function stringifySyncStep2 (y, decoder, strBuilder) {
|
export function stringifySyncStep2 (y, decoder, strBuilder) {
|
||||||
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
|
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
|
||||||
strBuilder.push(' == OS: \n')
|
strBuilder.push(' == OS: \n')
|
||||||
stringifyUpdate(y, decoder, strBuilder)
|
|
||||||
// write DS to string
|
// write DS to string
|
||||||
strBuilder.push(' == DS: \n')
|
strBuilder.push(' == DS: \n')
|
||||||
let len = decoder.readUint32()
|
let len = decoder.readUint32()
|
||||||
@ -20,6 +18,7 @@ export function stringifySyncStep2 (y, decoder, strBuilder) {
|
|||||||
strBuilder.push(`[${from}, ${to}, ${gc}]`)
|
strBuilder.push(`[${from}, ${to}, ${gc}]`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stringifyStructs(y, decoder, strBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readSyncStep2 (decoder, encoder, y, senderConn, sender) {
|
export function readSyncStep2 (decoder, encoder, y, senderConn, sender) {
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
import { getStruct } from '../Util/structReferences.js'
|
|
||||||
|
|
||||||
export function stringifyUpdate (y, decoder, strBuilder) {
|
|
||||||
while (decoder.length !== decoder.pos) {
|
|
||||||
let reference = decoder.readVarUint()
|
|
||||||
let Constr = getStruct(reference)
|
|
||||||
let struct = new Constr()
|
|
||||||
let missing = struct._fromBinary(y, decoder)
|
|
||||||
let logMessage = struct._logString()
|
|
||||||
if (missing.length > 0) {
|
|
||||||
logMessage += missing.map(m => m._logString()).join(', ')
|
|
||||||
}
|
|
||||||
logMessage += '\n'
|
|
||||||
strBuilder.push(logMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { integrateRemoteStructs as readUpdate } from './integrateRemoteStructs.js'
|
|
@ -15,7 +15,9 @@ export default class OperationStore extends Tree {
|
|||||||
struct = new Constr()
|
struct = new Constr()
|
||||||
struct._id = id
|
struct._id = id
|
||||||
struct._parent = y
|
struct._parent = y
|
||||||
struct._integrate(y)
|
y.transact(() => {
|
||||||
|
struct._integrate(y)
|
||||||
|
})
|
||||||
this.put(struct)
|
this.put(struct)
|
||||||
}
|
}
|
||||||
return struct
|
return struct
|
||||||
|
@ -2,6 +2,7 @@ import { getReference } from '../Util/structReferences.js'
|
|||||||
import ID from '../Util/ID.js'
|
import ID from '../Util/ID.js'
|
||||||
import { RootFakeUserID } from '../Util/RootID.js'
|
import { RootFakeUserID } from '../Util/RootID.js'
|
||||||
import Delete from './Delete.js'
|
import Delete from './Delete.js'
|
||||||
|
import { transactionTypeChanged } from '../Transaction.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper utility to split an Item (see _splitAt)
|
* Helper utility to split an Item (see _splitAt)
|
||||||
@ -64,10 +65,7 @@ export default class Item {
|
|||||||
del._length = this._length
|
del._length = this._length
|
||||||
del._integrate(y, true)
|
del._integrate(y, true)
|
||||||
}
|
}
|
||||||
const parent = this._parent
|
transactionTypeChanged(y, this._parent, this._parentSub)
|
||||||
if (parent !== y && !parent._deleted) {
|
|
||||||
y._transactionChangedTypes.set(parent, this._parentSub)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This is called right before this struct receives any children.
|
* This is called right before this struct receives any children.
|
||||||
@ -98,7 +96,7 @@ export default class Item {
|
|||||||
// missing content from user
|
// missing content from user
|
||||||
throw new Error('Can not apply yet!')
|
throw new Error('Can not apply yet!')
|
||||||
}
|
}
|
||||||
if (!parent._deleted && !y._transactionChangedTypes.has(parent) && !y._transactionNewTypes.has(parent)) {
|
if (!parent._deleted && !y._transaction.changedTypes.has(parent) && !y._transaction.newTypes.has(parent)) {
|
||||||
// this is the first time parent is updated
|
// this is the first time parent is updated
|
||||||
// or this types is new
|
// or this types is new
|
||||||
this._parent._beforeChange()
|
this._parent._beforeChange()
|
||||||
@ -178,10 +176,7 @@ export default class Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
y.os.put(this)
|
y.os.put(this)
|
||||||
if (parent !== y && !parent._deleted) {
|
transactionTypeChanged(y, parent, parentSub)
|
||||||
y._transactionChangedTypes.set(parent, parentSub)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._id.user !== RootFakeUserID) {
|
if (this._id.user !== RootFakeUserID) {
|
||||||
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
||||||
y.connector.broadcastStruct(this)
|
y.connector.broadcastStruct(this)
|
||||||
|
@ -29,7 +29,7 @@ export default class Type extends Item {
|
|||||||
this._eventHandler.removeEventListener(f)
|
this._eventHandler.removeEventListener(f)
|
||||||
}
|
}
|
||||||
_integrate (y) {
|
_integrate (y) {
|
||||||
y._transactionNewTypes.add(this)
|
y._transaction.newTypes.add(this)
|
||||||
super._integrate(y)
|
super._integrate(y)
|
||||||
this._y = y
|
this._y = y
|
||||||
// when integrating children we must make sure to
|
// when integrating children we must make sure to
|
||||||
@ -48,7 +48,7 @@ export default class Type extends Item {
|
|||||||
}
|
}
|
||||||
_delete (y, createDelete) {
|
_delete (y, createDelete) {
|
||||||
super._delete(y, createDelete)
|
super._delete(y, createDelete)
|
||||||
y._transactionChangedTypes.delete(this)
|
y._transaction.changedTypes.delete(this)
|
||||||
// delete map types
|
// delete map types
|
||||||
for (let value of this._map.values()) {
|
for (let value of this._map.values()) {
|
||||||
if (value instanceof Item && !value._deleted) {
|
if (value instanceof Item && !value._deleted) {
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import Type from '../Struct/Type.js'
|
import Type from '../Struct/Type.js'
|
||||||
import ItemJSON from '../Struct/ItemJSON.js'
|
import ItemJSON from '../Struct/ItemJSON.js'
|
||||||
|
|
||||||
|
class YArrayEvent {
|
||||||
|
constructor (yarray, remote) {
|
||||||
|
this.target = yarray
|
||||||
|
this.remote = remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class YArray extends Type {
|
export default class YArray extends Type {
|
||||||
_callObserver () {
|
_callObserver (parentSubs, remote) {
|
||||||
this._eventHandler.callEventListeners({})
|
this._eventHandler.callEventListeners(new YArrayEvent(this, remote))
|
||||||
}
|
}
|
||||||
get (i) {
|
get (i) {
|
||||||
// TODO: This can be improved!
|
// TODO: This can be improved!
|
||||||
@ -107,12 +114,13 @@ export default class YArray extends Type {
|
|||||||
}
|
}
|
||||||
item = item._right
|
item = item._right
|
||||||
}
|
}
|
||||||
if (length > 0) {
|
|
||||||
throw new Error('Delete exceeds the range of the YArray')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if (length > 0) {
|
||||||
|
throw new Error('Delete exceeds the range of the YArray')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
insertAfter (left, content) {
|
insertAfter (left, content) {
|
||||||
|
const y = this._y
|
||||||
const apply = () => {
|
const apply = () => {
|
||||||
let right
|
let right
|
||||||
if (left === null) {
|
if (left === null) {
|
||||||
@ -123,10 +131,13 @@ export default class YArray extends Type {
|
|||||||
let prevJsonIns = null
|
let prevJsonIns = null
|
||||||
for (let i = 0; i < content.length; i++) {
|
for (let i = 0; i < content.length; i++) {
|
||||||
let c = content[i]
|
let c = content[i]
|
||||||
|
if (typeof c === 'function') {
|
||||||
|
c = new c() // eslint-disable-line new-cap
|
||||||
|
}
|
||||||
if (c instanceof Type) {
|
if (c instanceof Type) {
|
||||||
if (prevJsonIns !== null) {
|
if (prevJsonIns !== null) {
|
||||||
if (this._y !== null) {
|
if (y !== null) {
|
||||||
prevJsonIns._integrate(this._y)
|
prevJsonIns._integrate(y)
|
||||||
}
|
}
|
||||||
left = prevJsonIns
|
left = prevJsonIns
|
||||||
prevJsonIns = null
|
prevJsonIns = null
|
||||||
@ -136,8 +147,8 @@ export default class YArray extends Type {
|
|||||||
c._right = right
|
c._right = right
|
||||||
c._right_origin = right
|
c._right_origin = right
|
||||||
c._parent = this
|
c._parent = this
|
||||||
if (this._y !== null) {
|
if (y !== null) {
|
||||||
c._integrate(this._y)
|
c._integrate(y)
|
||||||
} else if (left === null) {
|
} else if (left === null) {
|
||||||
this._start = c
|
this._start = c
|
||||||
}
|
}
|
||||||
@ -155,12 +166,12 @@ export default class YArray extends Type {
|
|||||||
prevJsonIns._content.push(c)
|
prevJsonIns._content.push(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prevJsonIns !== null && this._y !== null) {
|
if (prevJsonIns !== null && y !== null) {
|
||||||
prevJsonIns._integrate(this._y)
|
prevJsonIns._integrate(y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._y !== null) {
|
if (y !== null) {
|
||||||
this._y.transact(apply)
|
y.transact(apply)
|
||||||
} else {
|
} else {
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
@ -170,13 +181,19 @@ export default class YArray extends Type {
|
|||||||
let left = null
|
let left = null
|
||||||
let right = this._start
|
let right = this._start
|
||||||
let count = 0
|
let count = 0
|
||||||
|
const y = this._y
|
||||||
while (right !== null) {
|
while (right !== null) {
|
||||||
if (count <= pos && pos < count + right._length) {
|
const rightLen = right._deleted ? 0 : (right._length - 1)
|
||||||
right = right._splitAt(this._y, pos - count)
|
if (count <= pos && pos <= count + rightLen) {
|
||||||
|
const splitDiff = pos - count
|
||||||
|
right = right._splitAt(y, splitDiff)
|
||||||
left = right._left
|
left = right._left
|
||||||
|
count += splitDiff
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count += right._length
|
if (!right._deleted) {
|
||||||
|
count += right._length
|
||||||
|
}
|
||||||
left = right
|
left = right
|
||||||
right = right._right
|
right = right._right
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,17 @@ import Type from '../Struct/Type.js'
|
|||||||
import Item from '../Struct/Item.js'
|
import Item from '../Struct/Item.js'
|
||||||
import ItemJSON from '../Struct/ItemJSON.js'
|
import ItemJSON from '../Struct/ItemJSON.js'
|
||||||
|
|
||||||
|
class YMapEvent {
|
||||||
|
constructor (ymap, subs, remote) {
|
||||||
|
this.target = ymap
|
||||||
|
this.keysChanged = subs
|
||||||
|
this.remote = remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class YMap extends Type {
|
export default class YMap extends Type {
|
||||||
_callObserver (parentSub) {
|
_callObserver (parentSubs, remote) {
|
||||||
this._eventHandler.callEventListeners({
|
this._eventHandler.callEventListeners(new YMapEvent(this, parentSubs, remote))
|
||||||
name: parentSub
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
toJSON () {
|
toJSON () {
|
||||||
const map = {}
|
const map = {}
|
||||||
@ -36,13 +42,21 @@ export default class YMap extends Type {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
set (key, value) {
|
set (key, value) {
|
||||||
this._y.transact(() => {
|
const y = this._y
|
||||||
|
y.transact(() => {
|
||||||
const old = this._map.get(key) || null
|
const old = this._map.get(key) || null
|
||||||
if (old !== null) {
|
if (old !== null) {
|
||||||
old._delete(this._y)
|
if (old instanceof ItemJSON && old._content[0] === value) {
|
||||||
|
// Trying to overwrite with same value
|
||||||
|
// break here
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
old._delete(y)
|
||||||
}
|
}
|
||||||
let v
|
let v
|
||||||
if (value instanceof Item) {
|
if (typeof value === 'function') {
|
||||||
|
v = new value() // eslint-disable-line new-cap
|
||||||
|
} else if (value instanceof Item) {
|
||||||
v = value
|
v = value
|
||||||
} else {
|
} else {
|
||||||
v = new ItemJSON()
|
v = new ItemJSON()
|
||||||
@ -52,7 +66,7 @@ export default class YMap extends Type {
|
|||||||
v._right_origin = old
|
v._right_origin = old
|
||||||
v._parent = this
|
v._parent = this
|
||||||
v._parentSub = key
|
v._parentSub = key
|
||||||
v._integrate(this._y)
|
v._integrate(y)
|
||||||
})
|
})
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { defaultDomFilter, applyChangesFromDom, reflectChangesOnDom } from './ut
|
|||||||
|
|
||||||
import YArray from '../YArray.js'
|
import YArray from '../YArray.js'
|
||||||
import YXmlText from './YXmlText.js'
|
import YXmlText from './YXmlText.js'
|
||||||
|
import YXmlEvent from './YXmlEvent.js'
|
||||||
|
|
||||||
function domToYXml (parent, doms) {
|
function domToYXml (parent, doms) {
|
||||||
const types = []
|
const types = []
|
||||||
@ -65,22 +66,8 @@ export default class YXmlFragment extends YArray {
|
|||||||
xml.setDomFilter(f)
|
xml.setDomFilter(f)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_callObserver (parentSub) {
|
_callObserver (parentSubs, remote) {
|
||||||
let event
|
this._eventHandler.callEventListeners(new YXmlEvent(this, parentSubs, remote))
|
||||||
if (parentSub !== null) {
|
|
||||||
event = {
|
|
||||||
type: 'attributeChanged',
|
|
||||||
name: parentSub,
|
|
||||||
value: this.getAttribute(parentSub),
|
|
||||||
target: this
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event = {
|
|
||||||
type: 'contentChanged',
|
|
||||||
target: this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._eventHandler.callEventListeners(event)
|
|
||||||
}
|
}
|
||||||
toString () {
|
toString () {
|
||||||
return this.map(xml => xml.toString()).join('')
|
return this.map(xml => xml.toString()).join('')
|
||||||
|
@ -131,13 +131,17 @@ export function reflectChangesOnDom (event) {
|
|||||||
yxml._mutualExclude(() => {
|
yxml._mutualExclude(() => {
|
||||||
// TODO: do this once before applying stuff
|
// TODO: do this once before applying stuff
|
||||||
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
|
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
|
||||||
if (event.type === 'attributeChanged') {
|
|
||||||
if (event.value === undefined) {
|
// update attributes
|
||||||
dom.removeAttribute(event.name)
|
event.attributesChanged.forEach(attributeName => {
|
||||||
|
const value = yxml.getAttribute(attributeName)
|
||||||
|
if (value === undefined) {
|
||||||
|
dom.remoteAttribute(attributeName)
|
||||||
} else {
|
} else {
|
||||||
dom.setAttribute(event.name, event.value)
|
dom.setAttribute(attributeName, value)
|
||||||
}
|
}
|
||||||
} else if (event.type === 'contentChanged') {
|
})
|
||||||
|
if (event.childListChanged) {
|
||||||
// create fragment of undeleted nodes
|
// create fragment of undeleted nodes
|
||||||
const fragment = document.createDocumentFragment()
|
const fragment = document.createDocumentFragment()
|
||||||
yxml.forEach(function (t) {
|
yxml.forEach(function (t) {
|
||||||
|
31
src/Y.js
31
src/Y.js
@ -16,12 +16,7 @@ import { YXmlFragment, YXmlElement, YXmlText } from './Type/y-xml/y-xml.js'
|
|||||||
import BinaryDecoder from './Binary/Decoder.js'
|
import BinaryDecoder from './Binary/Decoder.js'
|
||||||
|
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
|
import Transaction from './Transaction.js'
|
||||||
function callTypesAfterTransaction (y) {
|
|
||||||
y._transactionChangedTypes.forEach(function (parentSub, type) {
|
|
||||||
type._callObserver(parentSub)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Y extends NamedEventHandler {
|
export default class Y extends NamedEventHandler {
|
||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
@ -42,25 +37,27 @@ export default class Y extends NamedEventHandler {
|
|||||||
this._missingStructs = new Map()
|
this._missingStructs = new Map()
|
||||||
this._readyToIntegrate = []
|
this._readyToIntegrate = []
|
||||||
this._transactionsInProgress = 0
|
this._transactionsInProgress = 0
|
||||||
// types added during transaction
|
this._transaction = null
|
||||||
this._transactionNewTypes = new Set()
|
|
||||||
// changed types (does not include new types)
|
|
||||||
this._transactionChangedTypes = new Map()
|
|
||||||
this.on('afterTransaction', callTypesAfterTransaction)
|
|
||||||
}
|
}
|
||||||
_beforeChange () {}
|
_beforeChange () {}
|
||||||
transact (f) {
|
transact (f, remote = false) {
|
||||||
this._transactionsInProgress++
|
let initialCall = this._transaction === null
|
||||||
|
if (initialCall) {
|
||||||
|
this._transaction = new Transaction(this)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
f()
|
f()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
this._transactionsInProgress--
|
if (initialCall) {
|
||||||
if (this._transactionsInProgress === 0) {
|
// emit change events on changed types
|
||||||
|
this._transaction.changedTypes.forEach(function (subs, type) {
|
||||||
|
type._callObserver(subs, remote)
|
||||||
|
})
|
||||||
|
this._transaction = null
|
||||||
|
// when all changes & events are processed, emit afterTransaction event
|
||||||
this.emit('afterTransaction', this)
|
this.emit('afterTransaction', this)
|
||||||
this._transactionChangedTypes = new Map()
|
|
||||||
this._transactionNewTypes = new Set()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fake _start for root properties (y.set('name', type))
|
// fake _start for root properties (y.set('name', type))
|
||||||
|
@ -125,24 +125,15 @@ test('insert & delete events', async function array8 (t) {
|
|||||||
})
|
})
|
||||||
array0.insert(0, [0, 1, 2])
|
array0.insert(0, [0, 1, 2])
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'insert',
|
remote: false
|
||||||
index: 0,
|
|
||||||
values: [0, 1, 2],
|
|
||||||
length: 3
|
|
||||||
})
|
})
|
||||||
array0.delete(0)
|
array0.delete(0)
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'delete',
|
remote: false
|
||||||
index: 0,
|
|
||||||
length: 1,
|
|
||||||
values: [0]
|
|
||||||
})
|
})
|
||||||
array0.delete(0, 2)
|
array0.delete(0, 2)
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'delete',
|
remote: false
|
||||||
index: 0,
|
|
||||||
length: 2,
|
|
||||||
values: [1, 2]
|
|
||||||
})
|
})
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
@ -155,19 +146,11 @@ test('insert & delete events for types', async function array9 (t) {
|
|||||||
})
|
})
|
||||||
array0.insert(0, [Y.Array])
|
array0.insert(0, [Y.Array])
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'insert',
|
remote: false
|
||||||
object: array0,
|
|
||||||
index: 0,
|
|
||||||
length: 1
|
|
||||||
})
|
})
|
||||||
var type = array0.get(0)
|
|
||||||
t.assert(type._model != null, 'Model of type is defined')
|
|
||||||
array0.delete(0)
|
array0.delete(0)
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'delete',
|
remote: false
|
||||||
object: array0,
|
|
||||||
index: 0,
|
|
||||||
length: 1
|
|
||||||
})
|
})
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
@ -180,31 +163,19 @@ test('insert & delete events for types (2)', async function array10 (t) {
|
|||||||
})
|
})
|
||||||
array0.insert(0, ['hi', Y.Map])
|
array0.insert(0, ['hi', Y.Map])
|
||||||
compareEvent(t, events[0], {
|
compareEvent(t, events[0], {
|
||||||
type: 'insert',
|
remote: false
|
||||||
object: array0,
|
|
||||||
index: 0,
|
|
||||||
length: 1,
|
|
||||||
values: ['hi']
|
|
||||||
})
|
|
||||||
compareEvent(t, events[1], {
|
|
||||||
type: 'insert',
|
|
||||||
object: array0,
|
|
||||||
index: 1,
|
|
||||||
length: 1
|
|
||||||
})
|
})
|
||||||
|
t.assert(events.length === 1, 'Event is triggered exactly once for insertion of two elements')
|
||||||
array0.delete(1)
|
array0.delete(1)
|
||||||
compareEvent(t, events[2], {
|
compareEvent(t, events[1], {
|
||||||
type: 'delete',
|
remote: false
|
||||||
object: array0,
|
|
||||||
index: 1,
|
|
||||||
length: 1
|
|
||||||
})
|
})
|
||||||
|
t.assert(events.length === 2, 'Event is triggered exactly once for deletion')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('garbage collector', async function gc1 (t) {
|
test('garbage collector', async function gc1 (t) {
|
||||||
var { users, array0 } = await initArrays(t, { users: 3 })
|
var { users, array0 } = await initArrays(t, { users: 3 })
|
||||||
|
|
||||||
array0.insert(0, ['x', 'y', 'z'])
|
array0.insert(0, ['x', 'y', 'z'])
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
users[0].disconnect()
|
users[0].disconnect()
|
||||||
@ -215,60 +186,29 @@ test('garbage collector', async function gc1 (t) {
|
|||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('event has correct value when setting a primitive on a YArray (same user)', async function array11 (t) {
|
test('event target is set correctly (local)', async function array11 (t) {
|
||||||
var { array0, users } = await initArrays(t, { users: 3 })
|
let { array0, users } = await initArrays(t, { users: 3 })
|
||||||
|
|
||||||
var event
|
var event
|
||||||
array0.observe(function (e) {
|
array0.observe(function (e) {
|
||||||
event = e
|
event = e
|
||||||
})
|
})
|
||||||
array0.insert(0, ['stuff'])
|
array0.insert(0, ['stuff'])
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
t.assert(event.target === array0, '"target" property is set correctly')
|
||||||
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
|
||||||
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('event has correct value when setting a primitive on a YArray (received from another user)', async function array12 (t) {
|
test('event target is set correctly (remote user)', async function array12 (t) {
|
||||||
var { users, array0, array1 } = await initArrays(t, { users: 3 })
|
let { array0, array1, users } = await initArrays(t, { users: 3 })
|
||||||
|
|
||||||
var event
|
var event
|
||||||
array0.observe(function (e) {
|
array0.observe(function (e) {
|
||||||
event = e
|
event = e
|
||||||
})
|
})
|
||||||
array1.insert(0, ['stuff'])
|
array1.insert(0, ['stuff'])
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
compareEvent(t, event, {
|
||||||
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
remote: true
|
||||||
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('event has correct value when setting a type on a YArray (same user)', async function array13 (t) {
|
|
||||||
var { array0, users } = await initArrays(t, { users: 3 })
|
|
||||||
|
|
||||||
var event
|
|
||||||
array0.observe(function (e) {
|
|
||||||
event = e
|
|
||||||
})
|
})
|
||||||
array0.insert(0, [Y.Array])
|
t.assert(event.target === array0, '"target" property is set correctly')
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
|
||||||
t.assert(event.values[0] != null, 'event.value exists')
|
|
||||||
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
test('event has correct value when setting a type on a YArray (ops received from another user)', async function array14 (t) {
|
|
||||||
var { users, array0, array1 } = await initArrays(t, { users: 3 })
|
|
||||||
|
|
||||||
var event
|
|
||||||
array0.observe(function (e) {
|
|
||||||
event = e
|
|
||||||
})
|
|
||||||
array1.insert(0, [Y.Array])
|
|
||||||
await flushAll(t, users)
|
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
|
||||||
t.assert(event.values[0] != null, 'event.value exists')
|
|
||||||
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -279,56 +219,62 @@ function getUniqueNumber () {
|
|||||||
|
|
||||||
var arrayTransactions = [
|
var arrayTransactions = [
|
||||||
function insert (t, user, chance) {
|
function insert (t, user, chance) {
|
||||||
|
const yarray = user.get('array', Y.Array)
|
||||||
var uniqueNumber = getUniqueNumber()
|
var uniqueNumber = getUniqueNumber()
|
||||||
var content = []
|
var content = []
|
||||||
var len = chance.integer({ min: 1, max: 4 })
|
var len = chance.integer({ min: 1, max: 4 })
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
content.push(uniqueNumber)
|
content.push(uniqueNumber)
|
||||||
}
|
}
|
||||||
var pos = chance.integer({ min: 0, max: user.share.array.length })
|
var pos = chance.integer({ min: 0, max: yarray.length })
|
||||||
user.share.array.insert(pos, content)
|
yarray.insert(pos, content)
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
function insertTypeArray (t, user, chance) {
|
function insertTypeArray (t, user, chance) {
|
||||||
var pos = chance.integer({ min: 0, max: user.share.array.length })
|
const yarray = user.get('array', Y.Array)
|
||||||
user.share.array.insert(pos, [Y.Array])
|
var pos = chance.integer({ min: 0, max: yarray.length })
|
||||||
var array2 = user.share.array.get(pos)
|
yarray.insert(pos, [Y.Array])
|
||||||
|
var array2 = yarray.get(pos)
|
||||||
array2.insert(0, [1, 2, 3, 4])
|
array2.insert(0, [1, 2, 3, 4])
|
||||||
},
|
},
|
||||||
function insertTypeMap (t, user, chance) {
|
function insertTypeMap (t, user, chance) {
|
||||||
var pos = chance.integer({ min: 0, max: user.share.array.length })
|
const yarray = user.get('array', Y.Array)
|
||||||
user.share.array.insert(pos, [Y.Map])
|
var pos = chance.integer({ min: 0, max: yarray.length })
|
||||||
var map = user.share.array.get(pos)
|
yarray.insert(pos, [Y.Map])
|
||||||
|
var map = yarray.get(pos)
|
||||||
map.set('someprop', 42)
|
map.set('someprop', 42)
|
||||||
map.set('someprop', 43)
|
map.set('someprop', 43)
|
||||||
map.set('someprop', 44)
|
map.set('someprop', 44)
|
||||||
},
|
},
|
||||||
function _delete (t, user, chance) {
|
function _delete (t, user, chance) {
|
||||||
var length = user.share.array._content.length
|
const yarray = user.get('array', Y.Array)
|
||||||
|
var length = yarray.length
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
var pos = chance.integer({ min: 0, max: length - 1 })
|
var somePos = chance.integer({ min: 0, max: length - 1 })
|
||||||
var delLength = chance.integer({ min: 1, max: Math.min(2, length - pos) })
|
var delLength = chance.integer({ min: 1, max: Math.min(2, length - somePos) })
|
||||||
if (user.share.array._content[pos].type != null) {
|
if (yarray instanceof Y.Array) {
|
||||||
if (chance.bool()) {
|
if (chance.bool()) {
|
||||||
var type = user.share.array.get(pos)
|
var type = yarray.get(somePos)
|
||||||
if (type instanceof Y.Array.typeDefinition.class) {
|
if (type.length > 0) {
|
||||||
if (type._content.length > 0) {
|
somePos = chance.integer({ min: 0, max: type.length - 1 })
|
||||||
pos = chance.integer({ min: 0, max: type._content.length - 1 })
|
delLength = chance.integer({ min: 0, max: Math.min(2, type.length - somePos) })
|
||||||
delLength = chance.integer({ min: 0, max: Math.min(2, type._content.length - pos) })
|
type.delete(somePos, delLength)
|
||||||
type.delete(pos, delLength)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
type.delete('someprop')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.share.array.delete(pos, delLength)
|
yarray.delete(somePos, delLength)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.share.array.delete(pos, delLength)
|
yarray.delete(somePos, delLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
]
|
]
|
||||||
|
|
||||||
|
test('y-array: Random tests (5)', async function randomArray5 (t) {
|
||||||
|
await applyRandomTests(t, arrayTransactions, 5)
|
||||||
|
})
|
||||||
|
|
||||||
test('y-array: Random tests (42)', async function randomArray42 (t) {
|
test('y-array: Random tests (42)', async function randomArray42 (t) {
|
||||||
await applyRandomTests(t, arrayTransactions, 42)
|
await applyRandomTests(t, arrayTransactions, 42)
|
||||||
})
|
})
|
||||||
|
@ -5,6 +5,7 @@ import yTest from './test-connector.js'
|
|||||||
import Chance from 'chance'
|
import Chance from 'chance'
|
||||||
import ItemJSON from '../src/Struct/ItemJSON.js'
|
import ItemJSON from '../src/Struct/ItemJSON.js'
|
||||||
import ItemString from '../src/Struct/ItemString.js'
|
import ItemString from '../src/Struct/ItemString.js'
|
||||||
|
import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
|
||||||
|
|
||||||
export const Y = _Y
|
export const Y = _Y
|
||||||
|
|
||||||
@ -86,8 +87,12 @@ export async function compareUsers (t, users) {
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
await wait()
|
await wait()
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
|
await wait()
|
||||||
|
await flushAll(t, users)
|
||||||
|
await wait()
|
||||||
|
await flushAll(t, users)
|
||||||
|
|
||||||
var userArrayValues = users.map(u => u.get('array', Y.Array).toJSON())
|
var userArrayValues = users.map(u => u.get('array', Y.Array).toJSON().map(val => JSON.stringify(val)))
|
||||||
var userMapValues = users.map(u => u.get('map', Y.Map).toJSON())
|
var userMapValues = users.map(u => u.get('map', Y.Map).toJSON())
|
||||||
var userXmlValues = users.map(u => u.get('xml', Y.Xml).toString())
|
var userXmlValues = users.map(u => u.get('xml', Y.Xml).toString())
|
||||||
|
|
||||||
@ -110,22 +115,21 @@ export async function compareUsers (t, users) {
|
|||||||
})
|
})
|
||||||
))
|
))
|
||||||
var data = users.map(u => {
|
var data = users.map(u => {
|
||||||
|
defragmentItemContent(u)
|
||||||
var data = {}
|
var data = {}
|
||||||
let ops = []
|
let ops = []
|
||||||
u.os.iterate(null, null, function (op) {
|
u.os.iterate(null, null, function (op) {
|
||||||
if (!op._deleted) {
|
const json = {
|
||||||
const json = {
|
id: op._id,
|
||||||
id: op._id,
|
left: op._left === null ? null : op._left._id,
|
||||||
left: op._left === null ? null : op._left._id,
|
right: op._right === null ? null : op._right._id,
|
||||||
right: op._right === null ? null : op._right._id,
|
length: op._length,
|
||||||
length: op._length,
|
deleted: op._deleted
|
||||||
deleted: op._deleted
|
|
||||||
}
|
|
||||||
if (op instanceof ItemJSON || op instanceof ItemString) {
|
|
||||||
json.content = op._content
|
|
||||||
}
|
|
||||||
ops.push(json)
|
|
||||||
}
|
}
|
||||||
|
if (op instanceof ItemJSON || op instanceof ItemString) {
|
||||||
|
json.content = op._content
|
||||||
|
}
|
||||||
|
ops.push(json)
|
||||||
})
|
})
|
||||||
data.os = ops
|
data.os = ops
|
||||||
data.ds = getDeleteSet(u)
|
data.ds = getDeleteSet(u)
|
||||||
@ -173,6 +177,13 @@ export async function initArrays (t, opts) {
|
|||||||
return attrs.filter(a => a !== 'hidden')
|
return attrs.filter(a => a !== 'hidden')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
y.on('afterTransaction', function () {
|
||||||
|
for (let missing of y._missingStructs.values()) {
|
||||||
|
if (Array.from(missing.values()).length > 0) {
|
||||||
|
console.error(new Error('Test check in "afterTransaction": missing should be empty!'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
result.array0.delete(0, result.array0.length)
|
result.array0.delete(0, result.array0.length)
|
||||||
if (result.users[0].connector.testRoom != null) {
|
if (result.users[0].connector.testRoom != null) {
|
||||||
@ -266,7 +277,7 @@ export async function applyRandomTests (t, mods, iterations) {
|
|||||||
// TODO: We do not gc all users as this does not work yet
|
// TODO: We do not gc all users as this does not work yet
|
||||||
// await garbageCollectUsers(t, users)
|
// await garbageCollectUsers(t, users)
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
await users[0].db.emptyGarbageCollector()
|
// await users[0].db.emptyGarbageCollector()
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
} else if (chance.bool({likelihood: 10})) {
|
} else if (chance.bool({likelihood: 10})) {
|
||||||
// 20%*!prev chance to flush some operations
|
// 20%*!prev chance to flush some operations
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* global Y */
|
/* global Y */
|
||||||
import { wait } from './helper'
|
import { wait } from './helper'
|
||||||
import { messageToString, messageToRoomname } from '../src/MessageHandler/messageToString'
|
import { messageToString } from '../src/MessageHandler/messageToString'
|
||||||
|
|
||||||
var rooms = {}
|
var rooms = {}
|
||||||
|
|
||||||
@ -14,8 +14,8 @@ export class TestRoom {
|
|||||||
this.users.set(userID, connector)
|
this.users.set(userID, connector)
|
||||||
for (let [uid, user] of this.users) {
|
for (let [uid, user] of this.users) {
|
||||||
if (uid !== userID && (user.role === 'master' || connector.role === 'master')) {
|
if (uid !== userID && (user.role === 'master' || connector.role === 'master')) {
|
||||||
connector.userJoined(uid, this.users.get(uid).role)
|
connector.userJoined(uid, user.role)
|
||||||
this.users.get(uid).userJoined(userID, connector.role)
|
user.userJoined(userID, connector.role)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,13 +38,13 @@ export class TestRoom {
|
|||||||
}
|
}
|
||||||
async flushAll (users) {
|
async flushAll (users) {
|
||||||
let flushing = true
|
let flushing = true
|
||||||
let allUserIds = Array.from(this.users.keys())
|
let allUsers = Array.from(this.users.values())
|
||||||
if (users == null) {
|
if (users == null) {
|
||||||
users = allUserIds.map(id => this.users.get(id).y)
|
users = allUsers.map(user => user.y)
|
||||||
}
|
}
|
||||||
while (flushing) {
|
while (flushing) {
|
||||||
await wait(10)
|
await wait(10)
|
||||||
let res = await Promise.all(allUserIds.map(id => this.users.get(id)._flushAll(users)))
|
let res = await Promise.all(allUsers.map(user => user._flushAll(users)))
|
||||||
flushing = res.some(status => status === 'flushing')
|
flushing = res.some(status => status === 'flushing')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,13 +83,16 @@ export default function extendTestConnector (Y) {
|
|||||||
for (let [user, conn] of this.connections) {
|
for (let [user, conn] of this.connections) {
|
||||||
console.log(` ${user}:`)
|
console.log(` ${user}:`)
|
||||||
for (let i = 0; i < conn.buffer.length; i++) {
|
for (let i = 0; i < conn.buffer.length; i++) {
|
||||||
console.log(formatYjsMessage(conn.buffer[i]))
|
console.log(messageToString(conn.buffer[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
this.testRoom.join(this)
|
this.testRoom.join(this)
|
||||||
return super.reconnect()
|
super.reconnect()
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.whenSynced(resolve)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
send (uid, message) {
|
send (uid, message) {
|
||||||
super.send(uid, message)
|
super.send(uid, message)
|
||||||
@ -116,20 +119,22 @@ export default function extendTestConnector (Y) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
receiveMessage (sender, m) {
|
receiveMessage (sender, m) {
|
||||||
if (this.y.userID !== sender && this.connections.has(sender)) {
|
setTimeout(() => {
|
||||||
var buffer = this.connections.get(sender).buffer
|
if (this.y.userID !== sender && this.connections.has(sender)) {
|
||||||
if (buffer == null) {
|
var buffer = this.connections.get(sender).buffer
|
||||||
buffer = this.connections.get(sender).buffer = []
|
if (buffer == null) {
|
||||||
|
buffer = this.connections.get(sender).buffer = []
|
||||||
|
}
|
||||||
|
buffer.push(m)
|
||||||
|
if (this.chance.bool({likelihood: 30})) {
|
||||||
|
// flush 1/2 with 30% chance
|
||||||
|
var flushLength = Math.round(buffer.length / 2)
|
||||||
|
buffer.splice(0, flushLength).forEach(m => {
|
||||||
|
super.receiveMessage(sender, m)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buffer.push(m)
|
}, 0)
|
||||||
if (this.chance.bool({likelihood: 30})) {
|
|
||||||
// flush 1/2 with 30% chance
|
|
||||||
var flushLength = Math.round(buffer.length / 2)
|
|
||||||
buffer.splice(0, flushLength).forEach(m => {
|
|
||||||
super.receiveMessage(sender, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async _flushAll (flushUsers) {
|
async _flushAll (flushUsers) {
|
||||||
if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) {
|
if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user