Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
744469d363 | ||
|
|
311dd50f1b | ||
|
|
89c5541ee6 | ||
|
|
28d8db86f0 | ||
|
|
0c34216ed0 | ||
|
|
9aa518bc14 | ||
|
|
27b1190a28 | ||
|
|
f3d8db491b | ||
|
|
e9905602f8 | ||
|
|
2b8154fa16 | ||
|
|
4b35de5ad5 |
@@ -334,6 +334,8 @@ or any of its children.
|
|||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>get(index:number)</code></b>
|
<b><code>get(index:number)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
|
<b><code>clear()</code></b>
|
||||||
|
<dd>Removes all elements from this YMap.</dd>
|
||||||
<b><code>clone():Y.Map</code></b>
|
<b><code>clone():Y.Map</code></b>
|
||||||
<dd>Clone this type into a fresh Yjs type.</dd>
|
<dd>Clone this type into a fresh Yjs type.</dd>
|
||||||
<b><code>toJSON():Object<string, Object|boolean|Array|string|number|Uint8Array></code></b>
|
<b><code>toJSON():Object<string, Object|boolean|Array|string|number|Uint8Array></code></b>
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.10",
|
"version": "13.5.11",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.10",
|
"version": "13.5.11",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.cjs",
|
"main": "./dist/yjs.cjs",
|
||||||
"module": "./dist/yjs.mjs",
|
"module": "./dist/yjs.mjs",
|
||||||
|
|||||||
@@ -679,6 +679,8 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|||||||
packJsonContent()
|
packJsonContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lengthExceeded = error.create('Length exceeded!')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
@@ -689,6 +691,9 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
||||||
|
if (index > parent._length) {
|
||||||
|
throw lengthExceeded
|
||||||
|
}
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
if (parent._searchMarker) {
|
if (parent._searchMarker) {
|
||||||
updateMarkerChanges(parent._searchMarker, index, content.length)
|
updateMarkerChanges(parent._searchMarker, index, content.length)
|
||||||
@@ -766,7 +771,7 @@ export const typeListDelete = (transaction, parent, index, length) => {
|
|||||||
n = n.right
|
n = n.right
|
||||||
}
|
}
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
throw error.create('array length exceeded')
|
throw lengthExceeded
|
||||||
}
|
}
|
||||||
if (parent._searchMarker) {
|
if (parent._searchMarker) {
|
||||||
updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */)
|
updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */)
|
||||||
|
|||||||
@@ -237,6 +237,21 @@ export class YMap extends AbstractType {
|
|||||||
return typeMapHas(this, key)
|
return typeMapHas(this, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements from this YMap.
|
||||||
|
*/
|
||||||
|
clear () {
|
||||||
|
if (this.doc !== null) {
|
||||||
|
transact(this.doc, transaction => {
|
||||||
|
this.forEach(function (value, key, map) {
|
||||||
|
typeMapDelete(transaction, map, key)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
/** @type {Map<string, any>} */ (this._prelimContent).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
YXmlFragment,
|
YXmlFragment,
|
||||||
transact,
|
transact,
|
||||||
typeMapDelete,
|
typeMapDelete,
|
||||||
|
typeMapHas,
|
||||||
typeMapSet,
|
typeMapSet,
|
||||||
typeMapGet,
|
typeMapGet,
|
||||||
typeMapGetAll,
|
typeMapGetAll,
|
||||||
@@ -160,6 +161,18 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
return /** @type {any} */ (typeMapGet(this, attributeName))
|
return /** @type {any} */ (typeMapGet(this, attributeName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether an attribute exists
|
||||||
|
*
|
||||||
|
* @param {String} attributeName The attribute name to check for existence.
|
||||||
|
* @return {boolean} whether the attribute exists.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
hasAttribute (attributeName) {
|
||||||
|
return /** @type {any} */ (typeMapHas(this, attributeName))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all attribute name/value pairs in a JSON Object.
|
* Returns all attribute name/value pairs in a JSON Object.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ import {
|
|||||||
DSEncoderV2,
|
DSEncoderV2,
|
||||||
DSDecoderV1,
|
DSDecoderV1,
|
||||||
DSEncoderV1,
|
DSEncoderV1,
|
||||||
|
mergeUpdates,
|
||||||
mergeUpdatesV2,
|
mergeUpdatesV2,
|
||||||
Skip,
|
Skip,
|
||||||
diffUpdateV2,
|
diffUpdateV2,
|
||||||
|
convertUpdateFormatV2ToV1,
|
||||||
DSDecoderV2, Doc, Transaction, GC, Item, StructStore // eslint-disable-line
|
DSDecoderV2, Doc, Transaction, GC, Item, StructStore // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@@ -523,15 +525,16 @@ export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8A
|
|||||||
writeStateAsUpdate(encoder, doc, targetStateVector)
|
writeStateAsUpdate(encoder, doc, targetStateVector)
|
||||||
const updates = [encoder.toUint8Array()]
|
const updates = [encoder.toUint8Array()]
|
||||||
// also add the pending updates (if there are any)
|
// also add the pending updates (if there are any)
|
||||||
// @todo support diffirent encoders
|
if (doc.store.pendingDs) {
|
||||||
if (encoder.constructor === UpdateEncoderV2) {
|
updates.push(doc.store.pendingDs)
|
||||||
if (doc.store.pendingDs) {
|
}
|
||||||
updates.push(doc.store.pendingDs)
|
if (doc.store.pendingStructs) {
|
||||||
}
|
updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector))
|
||||||
if (doc.store.pendingStructs) {
|
}
|
||||||
updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector))
|
if (updates.length > 1) {
|
||||||
}
|
if (encoder.constructor === UpdateEncoderV1) {
|
||||||
if (updates.length > 1) {
|
return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update)))
|
||||||
|
} else if (encoder.constructor === UpdateEncoderV2) {
|
||||||
return mergeUpdatesV2(updates)
|
return mergeUpdatesV2(updates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,16 +154,9 @@ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YD
|
|||||||
if (curr !== null) {
|
if (curr !== null) {
|
||||||
let size = 0
|
let size = 0
|
||||||
let currClient = curr.id.client
|
let currClient = curr.id.client
|
||||||
let currClock = 0
|
|
||||||
let stopCounting = curr.id.clock !== 0 // must start at 0
|
let stopCounting = curr.id.clock !== 0 // must start at 0
|
||||||
|
let currClock = stopCounting ? 0 : curr.id.clock + curr.length
|
||||||
for (; curr !== null; curr = updateDecoder.next()) {
|
for (; curr !== null; curr = updateDecoder.next()) {
|
||||||
// we ignore skips
|
|
||||||
if (curr.constructor === Skip) {
|
|
||||||
stopCounting = true
|
|
||||||
}
|
|
||||||
if (!stopCounting) {
|
|
||||||
currClock = curr.id.clock + curr.length
|
|
||||||
}
|
|
||||||
if (currClient !== curr.id.client) {
|
if (currClient !== curr.id.client) {
|
||||||
if (currClock !== 0) {
|
if (currClock !== 0) {
|
||||||
size++
|
size++
|
||||||
@@ -173,7 +166,15 @@ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YD
|
|||||||
encoding.writeVarUint(encoder.restEncoder, currClock)
|
encoding.writeVarUint(encoder.restEncoder, currClock)
|
||||||
}
|
}
|
||||||
currClient = curr.id.client
|
currClient = curr.id.client
|
||||||
stopCounting = false
|
currClock = 0
|
||||||
|
stopCounting = curr.id.clock !== 0
|
||||||
|
}
|
||||||
|
// we ignore skips
|
||||||
|
if (curr.constructor === Skip) {
|
||||||
|
stopCounting = true
|
||||||
|
}
|
||||||
|
if (!stopCounting) {
|
||||||
|
currClock = curr.id.clock + curr.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// write what we have
|
// write what we have
|
||||||
@@ -332,13 +333,19 @@ export const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = U
|
|||||||
|
|
||||||
if (currWrite !== null) {
|
if (currWrite !== null) {
|
||||||
let curr = /** @type {Item | GC | null} */ (currDecoder.curr)
|
let curr = /** @type {Item | GC | null} */ (currDecoder.curr)
|
||||||
|
let iterated = false
|
||||||
|
|
||||||
// iterate until we find something that we haven't written already
|
// iterate until we find something that we haven't written already
|
||||||
// remember: first the high client-ids are written
|
// remember: first the high client-ids are written
|
||||||
while (curr !== null && curr.id.clock + curr.length <= currWrite.struct.id.clock + currWrite.struct.length && curr.id.client >= currWrite.struct.id.client) {
|
while (curr !== null && curr.id.clock + curr.length <= currWrite.struct.id.clock + currWrite.struct.length && curr.id.client >= currWrite.struct.id.client) {
|
||||||
curr = currDecoder.next()
|
curr = currDecoder.next()
|
||||||
|
iterated = true
|
||||||
}
|
}
|
||||||
if (curr === null || curr.id.client !== firstClient) {
|
if (
|
||||||
|
curr === null || // current decoder is empty
|
||||||
|
curr.id.client !== firstClient || // check whether there is another decoder that has has updates from `firstClient`
|
||||||
|
(iterated && curr.id.clock > currWrite.struct.id.clock + currWrite.struct.length) // the above while loop was used and we are potentially missing updates
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,3 +521,33 @@ const finishLazyStructWriting = (lazyWriter) => {
|
|||||||
encoding.writeUint8Array(restEncoder, partStructs.restEncoder)
|
encoding.writeUint8Array(restEncoder, partStructs.restEncoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} update
|
||||||
|
* @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} YDecoder
|
||||||
|
* @param {typeof UpdateEncoderV2 | typeof UpdateEncoderV1 } YEncoder
|
||||||
|
*/
|
||||||
|
export const convertUpdateFormat = (update, YDecoder, YEncoder) => {
|
||||||
|
const updateDecoder = new YDecoder(decoding.createDecoder(update))
|
||||||
|
const lazyDecoder = new LazyStructReader(updateDecoder, false)
|
||||||
|
const updateEncoder = new YEncoder()
|
||||||
|
const lazyWriter = new LazyStructWriter(updateEncoder)
|
||||||
|
|
||||||
|
for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
|
||||||
|
writeStructToLazyStructWriter(lazyWriter, curr, 0)
|
||||||
|
}
|
||||||
|
finishLazyStructWriting(lazyWriter)
|
||||||
|
const ds = readDeleteSet(updateDecoder)
|
||||||
|
writeDeleteSet(updateEncoder, ds)
|
||||||
|
return updateEncoder.toUint8Array()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} update
|
||||||
|
*/
|
||||||
|
export const convertUpdateFormatV1ToV2 = update => convertUpdateFormat(update, UpdateDecoderV1, UpdateEncoderV2)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} update
|
||||||
|
*/
|
||||||
|
export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, UpdateDecoderV2, UpdateEncoderV1)
|
||||||
|
|||||||
@@ -240,5 +240,49 @@ export const testMergeUpdates2 = tc => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo be able to apply Skip structs to Yjs docs
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
export const testMergePendingUpdates = tc => {
|
||||||
|
const yDoc = new Y.Doc()
|
||||||
|
/**
|
||||||
|
* @type {Array<Uint8Array>}
|
||||||
|
*/
|
||||||
|
const serverUpdates = []
|
||||||
|
yDoc.on('update', (update, origin, c) => {
|
||||||
|
serverUpdates.splice(serverUpdates.length, 0, update)
|
||||||
|
})
|
||||||
|
const yText = yDoc.getText('textBlock')
|
||||||
|
yText.applyDelta([{ insert: 'r' }])
|
||||||
|
yText.applyDelta([{ insert: 'o' }])
|
||||||
|
yText.applyDelta([{ insert: 'n' }])
|
||||||
|
yText.applyDelta([{ insert: 'e' }])
|
||||||
|
yText.applyDelta([{ insert: 'n' }])
|
||||||
|
|
||||||
|
const yDoc1 = new Y.Doc()
|
||||||
|
Y.applyUpdate(yDoc1, serverUpdates[0])
|
||||||
|
const update1 = Y.encodeStateAsUpdate(yDoc1)
|
||||||
|
|
||||||
|
const yDoc2 = new Y.Doc()
|
||||||
|
Y.applyUpdate(yDoc2, update1)
|
||||||
|
Y.applyUpdate(yDoc2, serverUpdates[1])
|
||||||
|
const update2 = Y.encodeStateAsUpdate(yDoc2)
|
||||||
|
|
||||||
|
const yDoc3 = new Y.Doc()
|
||||||
|
Y.applyUpdate(yDoc3, update2)
|
||||||
|
Y.applyUpdate(yDoc3, serverUpdates[3])
|
||||||
|
const update3 = Y.encodeStateAsUpdate(yDoc3)
|
||||||
|
|
||||||
|
const yDoc4 = new Y.Doc()
|
||||||
|
Y.applyUpdate(yDoc4, update3)
|
||||||
|
Y.applyUpdate(yDoc4, serverUpdates[2])
|
||||||
|
const update4 = Y.encodeStateAsUpdate(yDoc4)
|
||||||
|
|
||||||
|
const yDoc5 = new Y.Doc()
|
||||||
|
Y.applyUpdate(yDoc5, update4)
|
||||||
|
Y.applyUpdate(yDoc5, serverUpdates[4])
|
||||||
|
// @ts-ignore
|
||||||
|
const update5 = Y.encodeStateAsUpdate(yDoc5) // eslint-disable-line
|
||||||
|
|
||||||
|
const yText5 = yDoc5.getText('textBlock')
|
||||||
|
t.compareStrings(yText5.toString(), 'nenor')
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,49 @@ export const testLengthIssue = tc => {
|
|||||||
t.assert(arr.length === arr.toArray().length)
|
t.assert(arr.length === arr.toArray().length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debugging yjs#314
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testLengthIssue2 = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const next = doc.getArray()
|
||||||
|
doc.transact(() => {
|
||||||
|
next.insert(0, ['group2'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
next.insert(1, ['rectangle3'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
next.delete(0)
|
||||||
|
next.insert(0, ['rectangle3'])
|
||||||
|
})
|
||||||
|
next.delete(1)
|
||||||
|
doc.transact(() => {
|
||||||
|
next.insert(1, ['ellipse4'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
next.insert(2, ['ellipse3'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
next.insert(3, ['ellipse2'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
doc.transact(() => {
|
||||||
|
t.fails(() => {
|
||||||
|
next.insert(5, ['rectangle2'])
|
||||||
|
})
|
||||||
|
next.insert(4, ['rectangle2'])
|
||||||
|
})
|
||||||
|
doc.transact(() => {
|
||||||
|
// this should not throw an error message
|
||||||
|
next.delete(4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(next.toArray())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -189,6 +189,49 @@ export const testGetAndSetAndDeleteOfMapProperty = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testSetAndClearOfMapProperties = tc => {
|
||||||
|
const { testConnector, users, map0 } = init(tc, { users: 1 })
|
||||||
|
map0.set('stuff', 'c0')
|
||||||
|
map0.set('otherstuff', 'c1')
|
||||||
|
map0.clear()
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
for (const user of users) {
|
||||||
|
const u = user.getMap('map')
|
||||||
|
t.assert(u.get('stuff') === undefined)
|
||||||
|
t.assert(u.get('otherstuff') === undefined)
|
||||||
|
t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`)
|
||||||
|
}
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testSetAndClearOfMapPropertiesWithConflicts = tc => {
|
||||||
|
const { testConnector, users, map0, map1, map2, map3 } = init(tc, { users: 4 })
|
||||||
|
map0.set('stuff', 'c0')
|
||||||
|
map1.set('stuff', 'c1')
|
||||||
|
map1.set('stuff', 'c2')
|
||||||
|
map2.set('stuff', 'c3')
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
map0.set('otherstuff', 'c0')
|
||||||
|
map1.set('otherstuff', 'c1')
|
||||||
|
map2.set('otherstuff', 'c2')
|
||||||
|
map3.set('otherstuff', 'c3')
|
||||||
|
map3.clear()
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
for (const user of users) {
|
||||||
|
const u = user.getMap('map')
|
||||||
|
t.assert(u.get('stuff') === undefined)
|
||||||
|
t.assert(u.get('otherstuff') === undefined)
|
||||||
|
t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`)
|
||||||
|
}
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@@ -335,6 +378,30 @@ export const testThrowsAddAndUpdateAndDeleteEvents = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testThrowsDeleteEventsOnClear = tc => {
|
||||||
|
const { users, map0 } = init(tc, { users: 2 })
|
||||||
|
/**
|
||||||
|
* @type {Object<string,any>}
|
||||||
|
*/
|
||||||
|
let event = {}
|
||||||
|
map0.observe(e => {
|
||||||
|
event = e // just put it on event, should be thrown synchronously anyway
|
||||||
|
})
|
||||||
|
// set values
|
||||||
|
map0.set('stuff', 4)
|
||||||
|
map0.set('otherstuff', new Y.Array())
|
||||||
|
// clear
|
||||||
|
map0.clear()
|
||||||
|
compareEvent(event, {
|
||||||
|
keysChanged: new Set(['stuff', 'otherstuff']),
|
||||||
|
target: map0
|
||||||
|
})
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,23 @@ export const testSetProperty = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testHasProperty = tc => {
|
||||||
|
const { testConnector, users, xml0, xml1 } = init(tc, { users: 2 })
|
||||||
|
xml0.setAttribute('height', '10')
|
||||||
|
t.assert(xml0.hasAttribute('height'), 'Simple set+has works')
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
t.assert(xml1.hasAttribute('height'), 'Simple set+has works (remote)')
|
||||||
|
|
||||||
|
xml0.removeAttribute('height')
|
||||||
|
t.assert(!xml0.hasAttribute('height'), 'Simple set+remove+has works')
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
t.assert(!xml1.hasAttribute('height'), 'Simple set+remove+has works (remote)')
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user