fix all tests

This commit is contained in:
Kevin Jahns 2018-11-16 12:33:20 +01:00
parent f94653424a
commit 52abcdd043
10 changed files with 110 additions and 63 deletions

View File

@ -1,5 +1,5 @@
import { createMutex } from '../../lib/mutex.js'
import { createMutex } from '../lib/mutex.js'
/**
* Abstract class for bindings.

View File

@ -1,6 +1,6 @@
/* global MutationObserver, getSelection */
import { fromRelativePosition } from '../../Util/relativePosition.js'
import { fromRelativePosition } from '../../src/Util/relativePosition.js'
import Binding from '../Binding.js'
import { createAssociation, removeAssociation } from './util.js'
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer, getCurrentRelativeSelection } from './selection.js'
@ -121,8 +121,15 @@ export default class DomBinding extends Binding {
createAssociation(this, target, type)
}
flushDomChanges () {
this._domObserver(this._mutationObserver.takeRecords())
}
/**
* NOTE: currently does not apply filter to existing elements!
* NOTE:
* * does not apply filter to existing elements!
* * only guarantees that changes are filtered locally. Remote sites may see different content.
*
* @param {DomFilter} filter The filter function to use from now on.
*/
setFilter (filter) {

View File

@ -1,11 +1,11 @@
import YXmlHook from '../../Types/YXml/YXmlHook.js'
import YXmlHook from '../../src/Types/YXml/YXmlHook.js'
import {
iterateUntilUndeleted,
removeAssociation,
insertNodeHelper } from './util.js'
import diff from '../../../lib/simpleDiff.js'
import YXmlFragment from '../../Types/YXml/YXmlFragment.js'
import diff from '../../lib/simpleDiff.js'
import YXmlFragment from '../../src/Types/YXml/YXmlFragment.js'
/**
* 1. Check if any of the nodes was deleted

View File

@ -47,10 +47,12 @@ export function splitHelper (y, a, b, diff) {
o = o._right
}
y.os.put(b)
if (y._transaction.newTypes.has(a)) {
y._transaction.newTypes.add(b)
} else if (y._transaction.deletedStructs.has(a)) {
y._transaction.deletedStructs.add(b)
if (y._transaction !== null) {
if (y._transaction.newTypes.has(a)) {
y._transaction.newTypes.add(b)
} else if (y._transaction.deletedStructs.has(a)) {
y._transaction.deletedStructs.add(b)
}
}
}
@ -117,7 +119,7 @@ export default class Item {
*/
_copy () {
const C = this.constructor
return C()
return new C()
}
/**

View File

@ -54,7 +54,7 @@ export const stringifyDeleteSet = (decoder) => {
const dsLength = decoding.readUint32(decoder)
for (let i = 0; i < dsLength; i++) {
str += ' -' + decoding.readVarUint(decoder) + ':\n' // decodes user
const dvLength = decoding.readVarUint(decoder)
const dvLength = decoding.readUint32(decoder)
for (let j = 0; j < dvLength; j++) {
str += `clock: ${decoding.readVarUint(decoder)}, length: ${decoding.readVarUint(decoder)}, gc: ${decoding.readUint8(decoder) === 1}\n`
}
@ -192,8 +192,8 @@ export const readDeleteSet = (decoder, y) => {
*/
export const stringifyStateSet = decoder => {
let s = 'State Set: '
readStateSet(decoder).forEach((user, userState) => {
s += `(${user}: ${userState}), `
readStateSet(decoder).forEach((clock, user) => {
s += `(${user}: ${clock}), `
})
return s
}
@ -209,7 +209,7 @@ export const writeStateSet = (encoder, y) => {
// write as fixed-size number to stay consistent with the other encode functions.
// => anytime we write the number of objects that follow, encode as fixed-size number.
encoding.writeUint32(encoder, state.size)
state.forEach((user, clock) => {
state.forEach((clock, user) => {
encoding.writeVarUint(encoder, user)
encoding.writeVarUint(encoder, clock)
})
@ -317,7 +317,9 @@ export const writeStructs = (encoder, y, ss) => {
const overlappingLeft = y.os.findPrev(minBound)
const rightID = overlappingLeft === null ? null : overlappingLeft._id
if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) {
const struct = overlappingLeft._clonePartial(clock - rightID.clock)
// TODO: only write partial content (only missing content)
// const struct = overlappingLeft._clonePartial(clock - rightID.clock)
const struct = overlappingLeft
struct._toBinary(encoder)
len++
}

View File

@ -13,8 +13,8 @@ function testEncoding (t, write, read, val) {
t.compare(val, result, 'Compare results')
}
const writeVarUint = (encoder, val) => encoder.writeVarUint(val)
const readVarUint = decoder => decoder.readVarUint()
const writeVarUint = (encoder, val) => encoding.writeVarUint(encoder, val)
const readVarUint = decoder => decoding.readVarUint(decoder)
test('varUint 1 byte', async function varUint1 (t) {
testEncoding(t, writeVarUint, readVarUint, 42)
@ -46,8 +46,8 @@ test('varUint random user id', async function varUintRandomUserId (t) {
testEncoding(t, writeVarUint, readVarUint, generateRandomUint32())
})
const writeVarString = (encoder, val) => encoder.writeVarString(val)
const readVarString = decoder => decoder.readVarString()
const writeVarString = (encoder, val) => encoding.writeVarString(encoder, val)
const readVarString = decoder => decoding.readVarString(decoder)
test('varString', async function varString (t) {
testEncoding(t, writeVarString, readVarString, 'hello')

View File

@ -65,6 +65,7 @@ test('insertions work in late sync', async function array4 (t) {
array2.insert(1, ['user2'])
await users[1].connect()
await users[2].connect()
testConnector.flushAllMessages()
await compareUsers(t, users)
})
@ -215,7 +216,7 @@ function getUniqueNumber () {
var arrayTransactions = [
function insert (t, user, prng) {
const yarray = user.get('array', Y.Array)
const yarray = user.define('array', Y.Array)
var uniqueNumber = getUniqueNumber()
var content = []
var len = random.int32(prng, 1, 4)
@ -226,14 +227,14 @@ var arrayTransactions = [
yarray.insert(pos, content)
},
function insertTypeArray (t, user, prng) {
const yarray = user.get('array', Y.Array)
const yarray = user.define('array', Y.Array)
var pos = random.int32(prng, 0, yarray.length)
yarray.insert(pos, [Y.Array])
var array2 = yarray.get(pos)
array2.insert(0, [1, 2, 3, 4])
},
function insertTypeMap (t, user, prng) {
const yarray = user.get('array', Y.Array)
const yarray = user.define('array', Y.Array)
var pos = random.int32(prng, 0, yarray.length)
yarray.insert(pos, [Y.Map])
var map = yarray.get(pos)
@ -242,7 +243,7 @@ var arrayTransactions = [
map.set('someprop', 44)
},
function _delete (t, user, prng) {
const yarray = user.get('array', Y.Array)
const yarray = user.define('array', Y.Array)
var length = yarray.length
if (length > 0) {
var somePos = random.int32(prng, 0, length - 1)

View File

@ -50,7 +50,7 @@ test('Basic get&set of Map property (converge via sync)', async function map1 (t
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.compare(u.get('stuff'), 'stuffy')
t.assert(u.get('undefined') === undefined, 'undefined')
t.compare(u.get('null'), null, 'null')
@ -94,7 +94,7 @@ test('Basic get&set of Map property (converge via update)', async function map5
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.compare(u.get('stuff'), 'stuffy')
}
await compareUsers(t, users)
@ -108,7 +108,7 @@ test('Basic get&set of Map property (handle conflict)', async function map6 (t)
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.compare(u.get('stuff'), 'c0')
}
await compareUsers(t, users)
@ -121,7 +121,7 @@ test('Basic get&set&delete of Map property (handle conflict)', async function ma
map1.set('stuff', 'c1')
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.assert(u.get('stuff') === undefined)
}
await compareUsers(t, users)
@ -135,7 +135,7 @@ test('Basic get&set of Map property (handle three conflicts)', async function ma
map2.set('stuff', 'c3')
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.compare(u.get('stuff'), 'c0')
}
await compareUsers(t, users)
@ -155,7 +155,7 @@ test('Basic get&set&delete of Map property (handle three conflicts)', async func
map3.set('stuff', 'c3')
testConnector.flushAllMessages()
for (let user of users) {
var u = user.get('map', Y.Map)
var u = user.define('map', Y.Map)
t.assert(u.get('stuff') === undefined)
}
await compareUsers(t, users)
@ -303,12 +303,12 @@ var mapTransactions = [
function set (t, user, prng) {
let key = random.oneOf(prng, ['one', 'two'])
var value = random.utf16String(prng)
user.get('map', Y.Map).set(key, value)
user.define('map', Y.Map).set(key, value)
},
function setType (t, user, prng) {
let key = random.oneOf(prng, ['one', 'two'])
var type = random.oneOf(prng, [new Y.Array(), new Y.Map()])
user.get('map', Y.Map).set(key, type)
user.define('map', Y.Map).set(key, type)
if (type instanceof Y.Array) {
type.insert(0, [1, 2, 3, 4])
} else {
@ -317,7 +317,7 @@ var mapTransactions = [
},
function _delete (t, user, prng) {
let key = random.oneOf(prng, ['one', 'two'])
user.get('map', Y.Map).delete(key)
user.define('map', Y.Map).delete(key)
}
]

View File

@ -61,20 +61,24 @@ test('attribute modifications (y -> dom)', async function xml2 (t) {
})
test('attribute modifications (dom -> y)', async function xml3 (t) {
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 })
dom0.setAttribute('height', '100px')
domBinding0.flushDomChanges()
t.assert(xml0.getAttribute('height') === '100px', 'setAttribute')
dom0.removeAttribute('height')
domBinding0.flushDomChanges()
t.assert(xml0.getAttribute('height') == null, 'removeAttribute')
dom0.setAttribute('class', 'stuffy stuff')
domBinding0.flushDomChanges()
t.assert(xml0.getAttribute('class') === 'stuffy stuff', 'set class attribute')
await compareUsers(t, users)
})
test('element insert (dom -> y)', async function xml4 (t) {
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 })
dom0.insertBefore(document.createTextNode('some text'), null)
dom0.insertBefore(document.createElement('p'), null)
domBinding0.flushDomChanges()
t.assert(xml0.get(0).toString() === 'some text', 'Retrieve Text Node')
t.assert(xml0.get(1).nodeName === 'P', 'Retrieve Element node')
await compareUsers(t, users)
@ -90,10 +94,12 @@ test('element insert (y -> dom)', async function xml5 (t) {
})
test('y on insert, then delete (dom -> y)', async function xml6 (t) {
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 })
dom0.insertBefore(document.createElement('p'), null)
domBinding0.flushDomChanges()
t.assert(xml0.length === 1, 'one node present')
dom0.childNodes[0].remove()
domBinding0.flushDomChanges()
t.assert(xml0.length === 0, 'no node present after delete')
await compareUsers(t, users)
})
@ -164,11 +170,13 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) {
})
test('move element to a different position', async function xml13 (t) {
var { testConnector, users, dom0, dom1 } = await initArrays(t, { users: 3 })
var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 })
dom0.append(document.createElement('div'))
dom0.append(document.createElement('h1'))
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
dom1.insertBefore(dom1.childNodes[0], null)
domBinding1.flushDomChanges()
t.assert(dom1.childNodes[0].nodeName === 'H1', 'div was deleted (user 0)')
t.assert(dom1.childNodes[1].nodeName === 'DIV', 'div was moved to the correct position (user 0)')
t.assert(dom1.childNodes[0].nodeName === 'H1', 'div was deleted (user 1)')
@ -189,6 +197,7 @@ test('filter node', async function xml14 (t) {
domBinding1.setFilter(domFilter)
dom0.append(document.createElement('div'))
dom0.append(document.createElement('h1'))
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
t.assert(dom1.childNodes.length === 1, 'Only one node was not transmitted')
t.assert(dom1.childNodes[0].nodeName === 'DIV', 'div node was transmitted')
@ -206,6 +215,7 @@ test('filter attribute', async function xml15 (t) {
dom0.setAttribute('hidden', 'true')
dom0.setAttribute('style', 'height: 30px')
dom0.setAttribute('data-me', '77')
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
t.assert(dom0.getAttribute('hidden') === 'true', 'User 0 still has the attribute')
t.assert(dom1.getAttribute('hidden') == null, 'User 1 did not receive update')
@ -215,7 +225,7 @@ test('filter attribute', async function xml15 (t) {
})
test('deep element insert', async function xml16 (t) {
var { testConnector, users, dom0, dom1 } = await initArrays(t, { users: 3 })
var { testConnector, users, dom0, dom1, domBinding0 } = await initArrays(t, { users: 3 })
let deepElement = document.createElement('p')
let boldElement = document.createElement('b')
let attrElement = document.createElement('img')
@ -225,6 +235,7 @@ test('deep element insert', async function xml16 (t) {
deepElement.append(attrElement)
dom0.append(deepElement)
let str0 = dom0.outerHTML
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
let str1 = dom1.outerHTML
t.compare(str0, str1, 'Dom string representation matches')
@ -253,8 +264,8 @@ test('treeWalker', async function xml17 (t) {
* Incoming changes that contain malicious attributes should be deleted.
*/
test('Filtering remote changes', async function xmlFilteringRemote (t) {
var { testConnector, users, xml0, xml1, domBinding0 } = await initArrays(t, { users: 3 })
domBinding0.setFilter(function (nodeName, attributes) {
var { testConnector, users, xml0, xml1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 })
const filter = (nodeName, attributes) => {
attributes.delete('malicious')
if (nodeName === 'HIDEME') {
return null
@ -263,7 +274,9 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) {
} else {
return attributes
}
})
}
domBinding0.setFilter(filter)
domBinding1.setFilter(filter)
let paragraph = new Y.XmlElement('p')
let hideMe = new Y.XmlElement('hideMe')
let span = new Y.XmlElement('span')
@ -275,13 +288,16 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) {
let tag2 = new Y.XmlElement('tag')
tag2.setAttribute('isHidden', 'true')
paragraph.insert(0, [tag2])
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
// check dom
domBinding0.typeToDom.get(paragraph).setAttribute('malicious', 'true')
domBinding0.typeToDom.get(span).setAttribute('malicious', 'true')
domBinding0.flushDomChanges()
// check incoming attributes
xml1.get(0).get(0).setAttribute('malicious', 'true')
xml1.insert(0, [new Y.XmlElement('hideMe')])
domBinding0.flushDomChanges()
testConnector.flushAllMessages()
await compareUsers(t, users)
@ -292,30 +308,36 @@ var xmlTransactions = [
function attributeChange (t, user, prng) {
// random.word generates non-empty words. prepend something
user.dom.setAttribute('_' + random.word(prng), random.word(prng))
user.domBinding.flushDomChanges()
},
function attributeChangeHidden (t, user, prng) {
user.dom.setAttribute('hidden', random.word(prng))
user.domBinding.flushDomChanges()
},
function insertText (t, user, prng) {
let dom = user.dom
var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null
dom.insertBefore(document.createTextNode(random.word(prng)), succ)
user.domBinding.flushDomChanges()
},
function insertHiddenDom (t, user, prng) {
let dom = user.dom
var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null
dom.insertBefore(document.createElement('hidden'), succ)
user.domBinding.flushDomChanges()
},
function insertDom (t, user, prng) {
let dom = user.dom
var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null
dom.insertBefore(document.createElement('my-' + random.word(prng)), succ)
user.domBinding.flushDomChanges()
},
function deleteChild (t, user, prng) {
let dom = user.dom
if (dom.childNodes.length > 0) {
var d = random.oneOf(prng, dom.childNodes)
d.remove()
user.domBinding.flushDomChanges()
}
},
function insertTextSecondLayer (t, user, prng) {
@ -324,6 +346,7 @@ var xmlTransactions = [
let dom2 = random.oneOf(prng, dom.children)
let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null
dom2.insertBefore(document.createTextNode(random.word(prng)), succ)
user.domBinding.flushDomChanges()
}
},
function insertDomSecondLayer (t, user, prng) {
@ -332,6 +355,7 @@ var xmlTransactions = [
let dom2 = random.oneOf(prng, dom.children)
let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null
dom2.insertBefore(document.createElement('my-' + random.word(prng)), succ)
user.domBinding.flushDomChanges()
}
},
function deleteChildSecondLayer (t, user, prng) {
@ -342,6 +366,7 @@ var xmlTransactions = [
let d = random.oneOf(prng, dom2.childNodes)
d.remove()
}
user.domBinding.flushDomChanges()
}
}
]

View File

@ -6,10 +6,12 @@ import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
import Quill from 'quill'
import GC from '../src/Struct/GC.js'
import * as random from '../lib/random/random.js'
import * as message from '../src/message.js'
import * as syncProtocol from '../src/protocols/syncProtocol.js'
import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js'
import { createMutex } from '../lib/mutex.js'
import QuillBinding from '../bindings/QuillBinding/QuillBinding.js'
import DomBinding from '../bindings/DomBinding/DomBinding.js'
export * from '../src/index.js'
@ -21,7 +23,7 @@ const afterTransaction = (y, transaction) => {
y.mMux(() => {
if (transaction.encodedStructsLen > 0) {
const encoder = encoding.createEncoder()
message.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
broadcastMessage(y, encoding.toBuffer(encoder))
}
})
@ -31,8 +33,9 @@ export class TestYInstance extends Y.Y {
/**
* @param {TestConnector} testConnector
*/
constructor (testConnector) {
constructor (testConnector, clientID) {
super()
this.userID = clientID // overwriting clientID
/**
* @type {TestConnector}
*/
@ -64,17 +67,19 @@ export class TestYInstance extends Y.Y {
*/
connect () {
if (!this.tc.onlineConns.has(this)) {
this.tc.onlineConns.add(this)
const encoder = encoding.createEncoder()
message.writeSyncStep1(encoder, this)
syncProtocol.writeSyncStep1(encoder, this)
// publish SyncStep1
broadcastMessage(this, encoding.toBuffer(encoder))
this.tc.onlineConns.forEach(remoteYInstance => {
// remote instance sends instance to this instance
const encoder = encoding.createEncoder()
message.writeSyncStep1(encoder, remoteYInstance)
this._receive(encoding.toBuffer(encoder), remoteYInstance)
if (remoteYInstance !== this) {
// remote instance sends instance to this instance
const encoder = encoding.createEncoder()
syncProtocol.writeSyncStep1(encoder, remoteYInstance)
this._receive(encoding.toBuffer(encoder), remoteYInstance)
}
})
this.tc.onlineConns.add(this)
}
}
/**
@ -117,9 +122,10 @@ export class TestConnector {
}
/**
* Create a new Y instance and add it to the list of connections
* @param {number} clientID
*/
createY () {
return new TestYInstance(this)
createY (clientID) {
return new TestYInstance(this, clientID)
}
/**
* Choose random connection and flush a random message from a random sender.
@ -139,8 +145,9 @@ export class TestConnector {
}
const encoder = encoding.createEncoder()
receiver.mMux(() => {
console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver))
// do not publish data created when this function is executed (could be ss2 or update message)
message.readMessage(decoding.createDecoder(m), encoder, receiver)
syncProtocol.readSyncMessage(decoding.createDecoder(m), encoder, receiver)
})
if (encoding.length(encoder) > 0) {
// send reply message
@ -202,12 +209,15 @@ export class TestConnector {
* @param {TestYInstance} y // publish message created by `y` to all other online clients
* @param {ArrayBuffer} m
*/
const broadcastMessage = (y, m) =>
y.tc.onlineConns.forEach(remoteYInstance => {
if (remoteYInstance !== y) {
remoteYInstance._receive(m, y)
}
})
const broadcastMessage = (y, m) => {
if (y.tc.onlineConns.has(y)) {
y.tc.onlineConns.forEach(remoteYInstance => {
if (remoteYInstance !== y) {
remoteYInstance._receive(m, y)
}
})
}
}
/**
* Convert DS to a proper DeleteSet of Map.
@ -295,7 +305,7 @@ export function compareUsers (t, users) {
data.os = ops
data.ds = getDeleteSet(u)
const ss = {}
u.ss.state.forEach((user, clock) => {
u.ss.state.forEach((clock, user) => {
ss[user] = clock
})
data.ss = ss
@ -347,20 +357,20 @@ export function initArrays (t, opts) {
const testConnector = new TestConnector(prng)
result.testConnector = testConnector
for (let i = 0; i < opts.users; i++) {
let y = testConnector.createY()
let y = testConnector.createY(i)
result.users.push(y)
result['array' + i] = y.define('array', Y.Array)
result['map' + i] = y.define('map', Y.Map)
const yxml = y.define('xml', Y.XmlElement)
result['xml' + i] = yxml
const dom = document.createElement('my-dom')
const domBinding = new Y.DomBinding(yxml, dom, { filter })
const domBinding = new DomBinding(yxml, dom, { filter })
result['domBinding' + i] = domBinding
result['dom' + i] = dom
const textType = y.define('text', Y.Text)
result['text' + i] = textType
const quill = new Quill(document.createElement('div'))
result['quillBinding' + i] = new Y.QuillBinding(textType, quill)
result['quillBinding' + i] = new QuillBinding(textType, quill)
result['quill' + i] = quill
y.quill = quill // put quill on the y object (so we can use it later)
y.dom = dom