fix all remaining tests (xml tests)
This commit is contained in:
parent
f4d85e2a3e
commit
4c1798e5fa
@ -11,6 +11,9 @@ const customModules = new Set([
|
|||||||
'y-dom',
|
'y-dom',
|
||||||
'y-prosemirror'
|
'y-prosemirror'
|
||||||
])
|
])
|
||||||
|
/**
|
||||||
|
* @type {Set<any>}
|
||||||
|
*/
|
||||||
const customLibModules = new Set([
|
const customLibModules = new Set([
|
||||||
// 'funlib',
|
// 'funlib',
|
||||||
// 'y-protocols'
|
// 'y-protocols'
|
||||||
|
413
tests/helper.js
413
tests/helper.js
@ -1,413 +0,0 @@
|
|||||||
|
|
||||||
import * as Y from '../src/index.js'
|
|
||||||
import { ItemJSON } from '../src/structs/ItemJSON.js'
|
|
||||||
import { ItemString } from '../src/structs/ItemString.js'
|
|
||||||
import { defragmentItemContent } from '../src/utils/defragmentItemContent.js'
|
|
||||||
import Quill from 'quill'
|
|
||||||
import { GC } from '../src/structs/GC.js'
|
|
||||||
import * as random from 'lib0/prng.js'
|
|
||||||
import * as syncProtocol from 'y-protocols/sync.js'
|
|
||||||
import * as encoding from 'lib0/encoding.js'
|
|
||||||
import * as decoding from 'lib0/decoding.js'
|
|
||||||
import { createMutex } from 'lib0/mutex.js'
|
|
||||||
import { QuillBinding } from 'y-quill'
|
|
||||||
import { DomBinding } from 'y-dom'
|
|
||||||
|
|
||||||
export * from '../src/index.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TestYInstance} y
|
|
||||||
* @param {Y.Transaction} transaction
|
|
||||||
*/
|
|
||||||
const afterTransaction = (y, transaction) => {
|
|
||||||
y.mMux(() => {
|
|
||||||
if (transaction.encodedStructsLen > 0) {
|
|
||||||
const encoder = encoding.createEncoder()
|
|
||||||
syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
|
|
||||||
broadcastMessage(y, encoding.toBuffer(encoder))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TestYInstance extends Y.Y {
|
|
||||||
/**
|
|
||||||
* @param {TestConnector} testConnector
|
|
||||||
*/
|
|
||||||
constructor (testConnector, clientID) {
|
|
||||||
super()
|
|
||||||
this.userID = clientID // overwriting clientID
|
|
||||||
/**
|
|
||||||
* @type {TestConnector}
|
|
||||||
*/
|
|
||||||
this.tc = testConnector
|
|
||||||
/**
|
|
||||||
* @type {Map<TestYInstance, Array<ArrayBuffer>>}
|
|
||||||
*/
|
|
||||||
this.receiving = new Map()
|
|
||||||
/**
|
|
||||||
* Message mutex
|
|
||||||
* @type {Function}
|
|
||||||
*/
|
|
||||||
this.mMux = createMutex()
|
|
||||||
testConnector.allConns.add(this)
|
|
||||||
// set up observe on local model
|
|
||||||
this.on('afterTransaction', afterTransaction)
|
|
||||||
this.connect()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Disconnect from TestConnector.
|
|
||||||
*/
|
|
||||||
disconnect () {
|
|
||||||
this.receiving = new Map()
|
|
||||||
this.tc.onlineConns.delete(this)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Append yourself to the list of known Y instances in testconnector.
|
|
||||||
* Also initiate sync with all clients.
|
|
||||||
*/
|
|
||||||
connect () {
|
|
||||||
if (!this.tc.onlineConns.has(this)) {
|
|
||||||
this.tc.onlineConns.add(this)
|
|
||||||
const encoder = encoding.createEncoder()
|
|
||||||
syncProtocol.writeSyncStep1(encoder, this)
|
|
||||||
// publish SyncStep1
|
|
||||||
broadcastMessage(this, encoding.toBuffer(encoder))
|
|
||||||
this.tc.onlineConns.forEach(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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Receive a message from another client. This message is only appended to the list of receiving messages.
|
|
||||||
* TestConnector decides when this client actually reads this message.
|
|
||||||
*
|
|
||||||
* @param {ArrayBuffer} message
|
|
||||||
* @param {TestYInstance} remoteClient
|
|
||||||
*/
|
|
||||||
_receive (message, remoteClient) {
|
|
||||||
let messages = this.receiving.get(remoteClient)
|
|
||||||
if (messages === undefined) {
|
|
||||||
messages = []
|
|
||||||
this.receiving.set(remoteClient, messages)
|
|
||||||
}
|
|
||||||
messages.push(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps track of TestYInstances.
|
|
||||||
*
|
|
||||||
* The TestYInstances add/remove themselves from the list of connections maintained in this object.
|
|
||||||
* I think it makes sense. Deal with it.
|
|
||||||
*/
|
|
||||||
export class TestConnector {
|
|
||||||
constructor (prng) {
|
|
||||||
/**
|
|
||||||
* @type {Set<TestYInstance>}
|
|
||||||
*/
|
|
||||||
this.allConns = new Set()
|
|
||||||
/**
|
|
||||||
* @type {Set<TestYInstance>}
|
|
||||||
*/
|
|
||||||
this.onlineConns = new Set()
|
|
||||||
/**
|
|
||||||
* @type {random.PRNG}
|
|
||||||
*/
|
|
||||||
this.prng = prng
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create a new Y instance and add it to the list of connections
|
|
||||||
* @param {number} clientID
|
|
||||||
*/
|
|
||||||
createY (clientID) {
|
|
||||||
return new TestYInstance(this, clientID)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Choose random connection and flush a random message from a random sender.
|
|
||||||
*
|
|
||||||
* If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise.
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
flushRandomMessage () {
|
|
||||||
const prng = this.prng
|
|
||||||
const conns = Array.from(this.onlineConns).filter(conn => conn.receiving.size > 0)
|
|
||||||
if (conns.length > 0) {
|
|
||||||
const receiver = random.oneOf(prng, conns)
|
|
||||||
const [sender, messages] = random.oneOf(prng, Array.from(receiver.receiving))
|
|
||||||
const m = messages.shift()
|
|
||||||
if (messages.length === 0) {
|
|
||||||
receiver.receiving.delete(sender)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
syncProtocol.readSyncMessage(decoding.createDecoder(m), encoder, receiver)
|
|
||||||
})
|
|
||||||
if (encoding.length(encoder) > 0) {
|
|
||||||
// send reply message
|
|
||||||
sender._receive(encoding.toBuffer(encoder), receiver)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return {boolean} True iff this function actually flushed something
|
|
||||||
*/
|
|
||||||
flushAllMessages () {
|
|
||||||
let didSomething = false
|
|
||||||
while (this.flushRandomMessage()) {
|
|
||||||
didSomething = true
|
|
||||||
}
|
|
||||||
return didSomething
|
|
||||||
}
|
|
||||||
reconnectAll () {
|
|
||||||
this.allConns.forEach(conn => conn.connect())
|
|
||||||
}
|
|
||||||
disconnectAll () {
|
|
||||||
this.allConns.forEach(conn => conn.disconnect())
|
|
||||||
}
|
|
||||||
syncAll () {
|
|
||||||
this.reconnectAll()
|
|
||||||
this.flushAllMessages()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return {boolean} Whether it was possible to disconnect a randon connection.
|
|
||||||
*/
|
|
||||||
disconnectRandom () {
|
|
||||||
if (this.onlineConns.size === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
random.oneOf(this.prng, Array.from(this.onlineConns)).disconnect()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return {boolean} Whether it was possible to reconnect a random connection.
|
|
||||||
*/
|
|
||||||
reconnectRandom () {
|
|
||||||
const reconnectable = []
|
|
||||||
this.allConns.forEach(conn => {
|
|
||||||
if (!this.onlineConns.has(conn)) {
|
|
||||||
reconnectable.push(conn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (reconnectable.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
random.oneOf(this.prng, reconnectable).connect()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TestYInstance} y // publish message created by `y` to all other online clients
|
|
||||||
* @param {ArrayBuffer} m
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @param {Y.Y} y
|
|
||||||
* @return {Object<number, Array<[number, number, boolean]>>}
|
|
||||||
*/
|
|
||||||
const getDeleteSet = y => {
|
|
||||||
/**
|
|
||||||
* @type {Object<number, Array<[number, number, boolean]>}
|
|
||||||
*/
|
|
||||||
var ds = {}
|
|
||||||
y.ds.iterate(null, null, n => {
|
|
||||||
var user = n._id.user
|
|
||||||
var counter = n._id.clock
|
|
||||||
var len = n.len
|
|
||||||
var gc = n.gc
|
|
||||||
var dv = ds[user]
|
|
||||||
if (dv === void 0) {
|
|
||||||
dv = []
|
|
||||||
ds[user] = dv
|
|
||||||
}
|
|
||||||
dv.push([counter, len, gc])
|
|
||||||
})
|
|
||||||
return ds
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. reconnect and flush all
|
|
||||||
* 2. user 0 gc
|
|
||||||
* 3. get type content
|
|
||||||
* 4. disconnect & reconnect all (so gc is propagated)
|
|
||||||
* 5. compare os, ds, ss
|
|
||||||
*
|
|
||||||
* @param {any} t
|
|
||||||
* @param {Array<TestYInstance>} users
|
|
||||||
*/
|
|
||||||
export const compareUsers = (t, users) => {
|
|
||||||
users.forEach(u => u.connect())
|
|
||||||
do {
|
|
||||||
users.forEach(u => {
|
|
||||||
// flush dom changes
|
|
||||||
u.domBinding._beforeTransactionHandler(null, null, false)
|
|
||||||
})
|
|
||||||
} while (users[0].tc.flushAllMessages())
|
|
||||||
|
|
||||||
var userArrayValues = users.map(u => u.define('array', Y.Array).toJSON().map(val => JSON.stringify(val)))
|
|
||||||
var userMapValues = users.map(u => u.define('map', Y.Map).toJSON())
|
|
||||||
var userXmlValues = users.map(u => u.define('xml', Y.XmlElement).toString())
|
|
||||||
var userTextValues = users.map(u => u.define('text', Y.Text).toDelta())
|
|
||||||
var userQuillValues = users.map(u => {
|
|
||||||
u.quill.update('yjs') // get latest changes
|
|
||||||
return u.quill.getContents().ops
|
|
||||||
})
|
|
||||||
|
|
||||||
var data = users.map(u => {
|
|
||||||
defragmentItemContent(u)
|
|
||||||
var data = {}
|
|
||||||
let ops = []
|
|
||||||
u.os.iterate(null, null, op => {
|
|
||||||
let json
|
|
||||||
if (op.constructor === GC) {
|
|
||||||
json = {
|
|
||||||
type: 'GC',
|
|
||||||
id: op._id,
|
|
||||||
length: op._length,
|
|
||||||
content: null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json = {
|
|
||||||
id: op._id,
|
|
||||||
left: op._left === null ? null : op._left._lastId,
|
|
||||||
right: op._right === null ? null : op._right._id,
|
|
||||||
length: op._length,
|
|
||||||
deleted: op._deleted,
|
|
||||||
parent: op._parent._id,
|
|
||||||
content: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (op instanceof ItemJSON || op instanceof ItemString) {
|
|
||||||
json.content = op._content
|
|
||||||
}
|
|
||||||
ops.push(json)
|
|
||||||
})
|
|
||||||
data.os = ops
|
|
||||||
data.ds = getDeleteSet(u)
|
|
||||||
const ss = {}
|
|
||||||
u.ss.state.forEach((clock, user) => {
|
|
||||||
ss[user] = clock
|
|
||||||
})
|
|
||||||
data.ss = ss
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
for (var i = 0; i < data.length - 1; i++) {
|
|
||||||
t.group(() => {
|
|
||||||
t.compare(userArrayValues[i].length, users[i].get('array').length, 'array length correctly computed')
|
|
||||||
t.compare(userArrayValues[i], userArrayValues[i + 1], 'array types')
|
|
||||||
t.compare(userMapValues[i], userMapValues[i + 1], 'map types')
|
|
||||||
t.compare(userXmlValues[i], userXmlValues[i + 1], 'xml types')
|
|
||||||
t.compare(userTextValues[i].map(a => a.insert).join('').length, users[i].get('text').length, 'text length correctly computed')
|
|
||||||
t.compare(userTextValues[i], userTextValues[i + 1], 'text types')
|
|
||||||
t.compare(userQuillValues[i], userQuillValues[i + 1], 'quill delta content')
|
|
||||||
t.compare(data[i].os, data[i + 1].os, 'os')
|
|
||||||
t.compare(data[i].ds, data[i + 1].ds, 'ds')
|
|
||||||
t.compare(data[i].ss, data[i + 1].ss, 'ss')
|
|
||||||
}, `Compare user${i} with user${i + 1}`)
|
|
||||||
}
|
|
||||||
users.forEach(user => {
|
|
||||||
if (user._missingStructs.size !== 0) {
|
|
||||||
t.fail('missing structs should mes empty!')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
users.map(u => u.destroy())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} nodeName
|
|
||||||
* @param {Map<string, string>} attrs
|
|
||||||
* @return {null|Map<string, string>}
|
|
||||||
*/
|
|
||||||
const filter = (nodeName, attrs) => {
|
|
||||||
if (nodeName === 'HIDDEN') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
attrs.delete('hidden')
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} t
|
|
||||||
* @param {any} opts
|
|
||||||
* @return {any}
|
|
||||||
*/
|
|
||||||
export const initArrays = (t, opts) => {
|
|
||||||
var result = {
|
|
||||||
users: []
|
|
||||||
}
|
|
||||||
var prng = opts.prng || random.createPRNG(t.getSeed())
|
|
||||||
const testConnector = new TestConnector(prng)
|
|
||||||
result.testConnector = testConnector
|
|
||||||
for (let i = 0; i < opts.users; i++) {
|
|
||||||
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 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 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
|
|
||||||
y.domBinding = domBinding
|
|
||||||
}
|
|
||||||
testConnector.syncAll()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export const applyRandomTests = (t, mods, iterations) => {
|
|
||||||
const prng = random.createPRNG(t.getSeed())
|
|
||||||
const result = initArrays(t, { users: 5, prng })
|
|
||||||
const { testConnector, users } = result
|
|
||||||
for (var i = 0; i < iterations; i++) {
|
|
||||||
if (random.int32(prng, 0, 100) <= 2) {
|
|
||||||
// 2% chance to disconnect/reconnect a random user
|
|
||||||
if (random.bool(prng)) {
|
|
||||||
testConnector.disconnectRandom()
|
|
||||||
} else {
|
|
||||||
testConnector.reconnectRandom()
|
|
||||||
}
|
|
||||||
} else if (random.int32(prng, 0, 100) <= 1) {
|
|
||||||
// 1% chance to flush all & garbagecollect
|
|
||||||
// TODO: We do not gc all users as this does not work yet
|
|
||||||
// await garbageCollectUsers(t, users)
|
|
||||||
testConnector.flushAllMessages()
|
|
||||||
// await users[0].db.emptyGarbageCollector() // TODO: reintroduce GC tests!
|
|
||||||
} else if (random.int32(prng, 0, 100) <= 50) {
|
|
||||||
// 50% chance to flush a random message
|
|
||||||
testConnector.flushRandomMessage()
|
|
||||||
}
|
|
||||||
let user = random.oneOf(prng, users)
|
|
||||||
var test = random.oneOf(prng, mods)
|
|
||||||
test(t, user, prng)
|
|
||||||
}
|
|
||||||
compareUsers(t, users)
|
|
||||||
return result
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script type="module" src="./DeleteStore.tests.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -6,8 +6,9 @@ import * as deleteStore from './DeleteStore.tests.js'
|
|||||||
import * as array from './y-array.tests.js'
|
import * as array from './y-array.tests.js'
|
||||||
import * as map from './y-map.tests.js'
|
import * as map from './y-map.tests.js'
|
||||||
import * as text from './y-text.tests.js'
|
import * as text from './y-text.tests.js'
|
||||||
|
import * as xml from './y-xml.tests.js'
|
||||||
|
|
||||||
if (isBrowser) {
|
if (isBrowser) {
|
||||||
log.createVConsole(document.body)
|
log.createVConsole(document.body)
|
||||||
}
|
}
|
||||||
runTests({ deleteStore, map, array, text })
|
runTests({ deleteStore, map, array, text, xml })
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { initArrays, compareUsers } from './helper.js'
|
import { init, compare } from './testHelper.js'
|
||||||
import { test } from 'cutest'
|
|
||||||
import * as Y from '../src/index.js'
|
import * as Y from '../src/index.js'
|
||||||
|
import * as t from 'lib0/testing.js'
|
||||||
|
|
||||||
test('set property', async function xml0 (t) {
|
export const testSetProperty = tc => {
|
||||||
var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 })
|
const { testConnector, users, xml0, xml1 } = init(tc, { users: 2 })
|
||||||
xml0.setAttribute('height', '10')
|
xml0.setAttribute('height', '10')
|
||||||
t.assert(xml0.getAttribute('height') === '10', 'Simple set+get works')
|
t.assert(xml0.getAttribute('height') === '10', 'Simple set+get works')
|
||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
t.assert(xml1.getAttribute('height') === '10', 'Simple set+get works (remote)')
|
t.assert(xml1.getAttribute('height') === '10', 'Simple set+get works (remote)')
|
||||||
await compareUsers(t, users)
|
compare(users)
|
||||||
})
|
}
|
||||||
|
|
||||||
test('events', async function xml1 (t) {
|
export const testEvents = tc => {
|
||||||
var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 })
|
const { testConnector, users, xml0, xml1 } = init(tc, { users: 2 })
|
||||||
var event = { attributesChanged: new Set() }
|
let event = { attributesChanged: new Set() }
|
||||||
var remoteEvent = { attributesChanged: new Set() }
|
let remoteEvent = { attributesChanged: new Set() }
|
||||||
xml0.observe(e => {
|
xml0.observe(e => {
|
||||||
delete e._content
|
delete e._content
|
||||||
delete e.nodes
|
delete e.nodes
|
||||||
@ -45,96 +45,11 @@ test('events', async function xml1 (t) {
|
|||||||
t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element')
|
t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element')
|
||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)')
|
t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)')
|
||||||
await compareUsers(t, users)
|
compare(users)
|
||||||
})
|
}
|
||||||
|
|
||||||
test('attribute modifications (y -> dom)', async function xml2 (t) {
|
export const testTreewalker = tc => {
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
const { users, xml0 } = init(tc, { users: 3 })
|
||||||
xml0.setAttribute('height', '100px')
|
|
||||||
t.assert(dom0.getAttribute('height') === '100px', 'setAttribute')
|
|
||||||
xml0.removeAttribute('height')
|
|
||||||
t.assert(dom0.getAttribute('height') == null, 'removeAttribute')
|
|
||||||
xml0.setAttribute('class', 'stuffy stuff')
|
|
||||||
t.assert(dom0.getAttribute('class') === 'stuffy stuff', 'set class attribute')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('element insert (y -> dom)', async function xml5 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlText('some text')])
|
|
||||||
xml0.insert(1, [new Y.XmlElement('p')])
|
|
||||||
t.assert(dom0.childNodes[0].textContent === 'some text', 'Retrieve Text node')
|
|
||||||
t.assert(dom0.childNodes[1].nodeName === 'P', 'Retrieve Element node')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('y on insert, then delete (y -> dom)', async function xml7 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlElement('p')])
|
|
||||||
t.assert(dom0.childNodes[0].nodeName === 'P', 'Get inserted element from dom')
|
|
||||||
xml0.delete(0, 1)
|
|
||||||
t.assert(dom0.childNodes.length === 0, '#childNodes is empty after delete')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('delete consecutive (1) (Text)', async function xml8 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')])
|
|
||||||
xml0.delete(1, 2)
|
|
||||||
t.assert(xml0.length === 1, 'check length (y)')
|
|
||||||
t.assert(dom0.childNodes.length === 1, 'check length (dom)')
|
|
||||||
t.assert(dom0.childNodes[0].textContent === '1', 'check content')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('delete consecutive (2) (Text)', async function xml9 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')])
|
|
||||||
xml0.delete(0, 1)
|
|
||||||
xml0.delete(1, 1)
|
|
||||||
t.assert(xml0.length === 1, 'check length (y)')
|
|
||||||
t.assert(dom0.childNodes.length === 1, 'check length (dom)')
|
|
||||||
t.assert(dom0.childNodes[0].textContent === '2', 'check content')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('delete consecutive (1) (Element)', async function xml10 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
|
||||||
xml0.delete(1, 2)
|
|
||||||
t.assert(xml0.length === 1, 'check length (y)')
|
|
||||||
t.assert(dom0.childNodes.length === 1, 'check length (dom)')
|
|
||||||
t.assert(dom0.childNodes[0].nodeName === 'A', 'check content')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('delete consecutive (2) (Element)', async function xml11 (t) {
|
|
||||||
var { users, xml0, dom0 } = await initArrays(t, { users: 3 })
|
|
||||||
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
|
||||||
xml0.delete(0, 1)
|
|
||||||
xml0.delete(1, 1)
|
|
||||||
t.assert(xml0.length === 1, 'check length (y)')
|
|
||||||
t.assert(dom0.childNodes.length === 1, 'check length (dom)')
|
|
||||||
t.assert(dom0.childNodes[0].nodeName === 'B', 'check content')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Receive a bunch of elements (with disconnect)', async function xml12 (t) {
|
|
||||||
var { testConnector, users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 })
|
|
||||||
users[1].disconnect()
|
|
||||||
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
|
||||||
xml0.insert(0, [new Y.XmlElement('X'), new Y.XmlElement('Y'), new Y.XmlElement('Z')])
|
|
||||||
await users[1].connect()
|
|
||||||
testConnector.flushAllMessages()
|
|
||||||
t.assert(xml0.length === 6, 'check length (y)')
|
|
||||||
t.assert(xml1.length === 6, 'check length (y) (reconnected user)')
|
|
||||||
t.assert(dom0.childNodes.length === 6, 'check length (dom)')
|
|
||||||
t.assert(dom1.childNodes.length === 6, 'check length (dom) (reconnected user)')
|
|
||||||
await compareUsers(t, users)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('treeWalker', async function xml17 (t) {
|
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
|
||||||
let paragraph1 = new Y.XmlElement('p')
|
let paragraph1 = new Y.XmlElement('p')
|
||||||
let paragraph2 = new Y.XmlElement('p')
|
let paragraph2 = new Y.XmlElement('p')
|
||||||
let text1 = new Y.XmlText('init')
|
let text1 = new Y.XmlText('init')
|
||||||
@ -146,5 +61,5 @@ test('treeWalker', async function xml17 (t) {
|
|||||||
t.assert(allParagraphs[0] === paragraph1, 'querySelectorAll found paragraph1')
|
t.assert(allParagraphs[0] === paragraph1, 'querySelectorAll found paragraph1')
|
||||||
t.assert(allParagraphs[1] === paragraph2, 'querySelectorAll found paragraph2')
|
t.assert(allParagraphs[1] === paragraph2, 'querySelectorAll found paragraph2')
|
||||||
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
|
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
|
||||||
await compareUsers(t, users)
|
compare(users)
|
||||||
})
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user