restructuring the project

This commit is contained in:
Kevin Jahns
2019-03-01 23:26:40 +01:00
parent f6b4819ae3
commit 75f4a0a5f0
163 changed files with 6423 additions and 10770 deletions

View File

@@ -1,7 +1,7 @@
import { test } from 'cutest'
import * as random from '../lib/prng/prng.js'
import { DeleteStore } from '../utils/DeleteStore.js'
import * as ID from '../utils/ID.js'
import * as prng from 'funlib/prng.js'
import * as t from 'funlib/testing.js'
import { DeleteStore } from '../src/utils/DeleteStore.js'
import * as ID from '../src/utils/ID.js'
/**
* Converts a DS to an array of length 10.
@@ -13,9 +13,9 @@ import * as ID from '../utils/ID.js'
* ds.mark(ID.createID(0, 3), 1, false)
* dsToArray(ds) // => [0, 1, undefined, 0]
*
* @return {Array<(undefined|number)>} Array of numbers indicating if array[i] is deleted (0), garbage collected (1), or undeleted (undefined).
* @return {Array<(null|number)>} Array of numbers indicating if array[i] is deleted (0), garbage collected (1), or undeleted (undefined).
*/
function dsToArray (ds) {
const dsToArray = ds => {
const array = []
let i = 0
ds.iterate(null, null, n => {
@@ -30,53 +30,58 @@ function dsToArray (ds) {
return array
}
test('DeleteStore', async function ds1 (t) {
/**
* @param {t.TestCase} tc
*/
export const testDeleteStore = tc => {
t.describe('Integrity tests')
const ds = new DeleteStore()
ds.mark(ID.createID(0, 1), 1, false)
ds.mark(ID.createID(0, 2), 1, false)
ds.mark(ID.createID(0, 3), 1, false)
t.compare(dsToArray(ds), [null, 0, 0, 0])
t.compareArrays(dsToArray(ds), [null, 0, 0, 0])
ds.mark(ID.createID(0, 2), 1, true)
t.compare(dsToArray(ds), [null, 0, 1, 0])
t.compareArrays(dsToArray(ds), [null, 0, 1, 0])
ds.mark(ID.createID(0, 1), 1, true)
t.compare(dsToArray(ds), [null, 1, 1, 0])
t.compareArrays(dsToArray(ds), [null, 1, 1, 0])
ds.mark(ID.createID(0, 3), 1, true)
t.compare(dsToArray(ds), [null, 1, 1, 1])
t.compareArrays(dsToArray(ds), [null, 1, 1, 1])
ds.mark(ID.createID(0, 5), 1, true)
ds.mark(ID.createID(0, 4), 1, true)
t.compare(dsToArray(ds), [null, 1, 1, 1, 1, 1])
t.compareArrays(dsToArray(ds), [null, 1, 1, 1, 1, 1])
ds.mark(ID.createID(0, 0), 3, false)
t.compare(dsToArray(ds), [0, 0, 0, 1, 1, 1])
})
t.compareArrays(dsToArray(ds), [0, 0, 0, 1, 1, 1])
test('random DeleteStore tests', async function randomDS (t) {
const prng = random.createPRNG(t.getSeed())
const ds = new DeleteStore()
const dsArray = []
for (let i = 0; i < 200; i++) {
const pos = random.int32(prng, 0, 10)
const len = random.int32(prng, 0, 4)
const gc = random.bool(prng)
ds.mark(ID.createID(0, pos), len, gc)
for (let j = 0; j < len; j++) {
dsArray[pos + j] = gc ? 1 : 0
t.describe('Random tests')
const gen = tc.prng
for (let i = 0; i < tc.repititions; i++) {
const ds = new DeleteStore()
const dsArray = []
for (let i = 0; i < 200; i++) {
const pos = prng.int32(gen, 0, 10)
const len = prng.int32(gen, 0, 4)
const gc = prng.bool(gen)
ds.mark(ID.createID(0, pos), len, gc)
for (let j = 0; j < len; j++) {
dsArray[pos + j] = gc ? 1 : 0
}
}
}
// fill empty fields
for (let i = 0; i < dsArray.length; i++) {
if (dsArray[i] !== 0 && dsArray[i] !== 1) {
dsArray[i] = null
// fill empty fields
for (let i = 0; i < dsArray.length; i++) {
if (dsArray[i] !== 0 && dsArray[i] !== 1) {
dsArray[i] = null
}
}
}
t.compare(dsToArray(ds), dsArray, 'expected DS result')
let size = 0
let lastEl = null
for (let i = 0; i < dsArray.length; i++) {
let el = dsArray[i]
if (lastEl !== el && el !== null) {
size++
t.compareArrays(dsToArray(ds), dsArray, 'Expected DS result')
let size = 0
let lastEl = null
for (let i = 0; i < dsArray.length; i++) {
let el = dsArray[i]
if (lastEl !== el && el !== null) {
size++
}
lastEl = el
}
lastEl = el
t.assert(size === ds.length, 'DS sizes match')
}
t.compare(size, ds.length, 'expected ds size')
})
}

View File

@@ -1,29 +0,0 @@
import { test } from 'cutest'
import { simpleDiff } from '../lib/diff.js'
import * as random from '../lib/prng/prng.js'
function runDiffTest (t, a, b, expected) {
let result = simpleDiff(a, b)
t.compare(result, expected, `Compare "${a}" with "${b}"`)
}
test('diff tests', async function diff1 (t) {
runDiffTest(t, 'abc', 'axc', { pos: 1, remove: 1, insert: 'x' })
runDiffTest(t, 'bc', 'xc', { pos: 0, remove: 1, insert: 'x' })
runDiffTest(t, 'ab', 'ax', { pos: 1, remove: 1, insert: 'x' })
runDiffTest(t, 'b', 'x', { pos: 0, remove: 1, insert: 'x' })
runDiffTest(t, '', 'abc', { pos: 0, remove: 0, insert: 'abc' })
runDiffTest(t, 'abc', 'xyz', { pos: 0, remove: 3, insert: 'xyz' })
runDiffTest(t, 'axz', 'au', { pos: 1, remove: 2, insert: 'u' })
runDiffTest(t, 'ax', 'axy', { pos: 2, remove: 0, insert: 'y' })
})
test('random diff tests', async function randomDiff (t) {
const gen = random.createPRNG(t.getSeed() * 1000000000)
let a = random.word(gen)
let b = random.word(gen)
let change = simpleDiff(a, b)
let arr = Array.from(a)
arr.splice(change.pos, change.remove, ...Array.from(change.insert))
t.assert(arr.join('') === b, 'Applying change information is correct')
})

View File

@@ -1,65 +0,0 @@
import { test } from 'cutest'
import { generateRandomUint32 } from '../utils/generateRandomUint32.js'
import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js'
import * as random from '../lib/prng/prng.js'
function testEncoding (t, write, read, val) {
let encoder = encoding.createEncoder()
write(encoder, val)
let reader = decoding.createDecoder(encoding.toBuffer(encoder))
let result = read(reader)
t.log(`string encode: ${JSON.stringify(val).length} bytes / binary encode: ${encoding.length(encoder)} bytes`)
t.compare(val, result, 'Compare results')
}
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)
})
test('varUint 2 bytes', async function varUint2 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3)
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3)
})
test('varUint 3 bytes', async function varUint3 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3)
})
test('varUint 4 bytes', async function varUint4 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3)
})
test('varUint of 2839012934', async function varUint2839012934 (t) {
testEncoding(t, writeVarUint, readVarUint, 2839012934)
})
test('varUint random', async function varUintRandom (t) {
const prng = random.createPRNG(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER, 2))
testEncoding(t, writeVarUint, readVarUint, random.int32(prng, 0, (1 << 28) - 1))
})
test('varUint random user id', async function varUintRandomUserId (t) {
t.getSeed() // enforces that this test is repeated
testEncoding(t, writeVarUint, readVarUint, generateRandomUint32())
})
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')
testEncoding(t, writeVarString, readVarString, 'test!')
testEncoding(t, writeVarString, readVarString, '☺☺☺')
testEncoding(t, writeVarString, readVarString, '1234')
testEncoding(t, writeVarString, readVarString, '쾟')
testEncoding(t, writeVarString, readVarString, '龟') // surrogate length 3
testEncoding(t, writeVarString, readVarString, '😝') // surrogate length 4
})
test('varString random', async function varStringRandom (t) {
const prng = random.createPRNG(t.getSeed() * 10000000)
testEncoding(t, writeVarString, readVarString, random.utf16String(prng))
})

View File

@@ -1,19 +1,19 @@
import * as Y from '../index.js'
import { ItemJSON } from '../structs/ItemJSON.js'
import { ItemString } from '../structs/ItemString.js'
import { defragmentItemContent } from '../utils/defragmentItemContent.js'
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 '../structs/GC.js'
import * as random from '../lib/prng/prng.js'
import * as syncProtocol from '../protocols/sync.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/quill.js'
import { DomBinding } from '../bindings/dom/DomBinding.js'
import { GC } from '../src/structs/GC.js'
import * as random from 'funlib/prng.js'
import * as syncProtocol from 'y-protocols/sync.js'
import * as encoding from 'funlib/encoding.js'
import * as decoding from 'funlib/decoding.js'
import { createMutex } from 'funlib/mutex.js'
import { QuillBinding } from 'y-quill'
import { DomBinding } from 'y-dom'
export * from '../index.js'
export * from '../src/index.js'
/**
* @param {TestYInstance} y

View File

@@ -3,6 +3,6 @@
<head>
</head>
<body>
<script type="module" src="./diff.tests.js"></script>
<script type="module" src="./DeleteStore.tests.js"></script>
</body>
</html>

View File

@@ -1,9 +1,10 @@
// TODO: include all tests
import './red-black-tree.js'
import './y-array.tests.js'
import './y-text.tests.js'
import './y-map.tests.js'
import './y-xml.tests.js'
import './encode-decode.tests.js'
import './diff.tests.js'
import './prosemirror.test.js'
import { runTests } from 'funlib/testing.js'
import * as deleteStore from './DeleteStore.tests.js'
import { isBrowser } from 'funlib/environment.js'
import * as log from 'funlib/logging.js'
if (isBrowser) {
log.createVConsole(document.body)
}
runTests({ deleteStore })

View File

@@ -1,37 +0,0 @@
import { test } from 'cutest'
import * as random from '../lib/prng/prng.js'
import * as Y from '../index.js'
import { prosemirrorPlugin } from '../bindings/prosemirror.js'
import {EditorState} from 'prosemirror-state'
import {EditorView} from 'prosemirror-view'
import {schema} from 'prosemirror-schema-basic'
import {exampleSetup} from 'prosemirror-example-setup'
const createNewProsemirrorView = y => {
const view = new EditorView(document.createElement('div'), {
state: EditorState.create({
schema,
plugins: exampleSetup({schema}).concat([prosemirrorPlugin(y.define('prosemirror', Y.XmlFragment))])
})
})
return view
}
test('random prosemirror insertions', async t => {
const gen = random.createPRNG(t.getSeed())
const y = new Y.Y()
const p1 = createNewProsemirrorView(y)
const p2 = createNewProsemirrorView(y)
for (let i = 0; i < 10; i++) {
const p = random.oneOf(gen, [p1, p2])
const insertPos = random.int32(gen, 0, p.state.doc.content.size)
const overwrite = random.int32(gen, 0, p.state.doc.content.size - insertPos)
p.dispatch(p.state.tr.insertText('' + i, insertPos, insertPos + overwrite))
}
t.compare(
p1.state.doc.toJSON(),
p2.state.doc.toJSON(),
'compare prosemirror models'
)
})

View File

@@ -1,192 +0,0 @@
import { Tree as RedBlackTree } from '../lib/Tree.js'
import * as ID from '../utils/ID.js'
import { test, proxyConsole } from 'cutest'
import * as random from '../lib/prng/prng.js'
proxyConsole()
var numberOfRBTreeTests = 10000
const checkRedNodesDoNotHaveBlackChildren = (t, tree) => {
let correct = true
const traverse = n => {
if (n == null) {
return
}
if (n.isRed()) {
if (n.left != null) {
correct = correct && !n.left.isRed()
}
if (n.right != null) {
correct = correct && !n.right.isRed()
}
}
traverse(n.left)
traverse(n.right)
}
traverse(tree.root)
t.assert(correct, 'Red nodes do not have black children')
}
const checkBlackHeightOfSubTreesAreEqual = (t, tree) => {
let correct = true
const traverse = n => {
if (n == null) {
return 0
}
var sub1 = traverse(n.left)
var sub2 = traverse(n.right)
if (sub1 !== sub2) {
correct = false
}
if (n.isRed()) {
return sub1
} else {
return sub1 + 1
}
}
traverse(tree.root)
t.assert(correct, 'Black-height of sub-trees are equal')
}
const checkRootNodeIsBlack = (t, tree) => {
t.assert(tree.root == null || tree.root.isBlack(), 'root node is black')
}
test('RedBlack Tree', async function redBlackTree (t) {
let tree = new RedBlackTree()
tree.put({_id: ID.createID(8433, 0)})
tree.put({_id: ID.createID(12844, 0)})
tree.put({_id: ID.createID(1795, 0)})
tree.put({_id: ID.createID(30302, 0)})
tree.put({_id: ID.createID(64287)})
tree.delete(ID.createID(8433, 0))
tree.put({_id: ID.createID(28996)})
tree.delete(ID.createID(64287))
tree.put({_id: ID.createID(22721)})
checkRootNodeIsBlack(t, tree)
checkBlackHeightOfSubTreesAreEqual(t, tree)
checkRedNodesDoNotHaveBlackChildren(t, tree)
})
test(`random tests (${numberOfRBTreeTests})`, async function randomRBTree (t) {
let prng = random.createPRNG(t.getSeed() * 1000000000)
let tree = new RedBlackTree()
let elements = []
for (var i = 0; i < numberOfRBTreeTests; i++) {
if (random.int32(prng, 0, 100) < 80) {
// 80% chance to insert an element
let obj = ID.createID(random.int32(prng, 0, numberOfRBTreeTests), random.int32(prng, 0, 1))
let nodeExists = tree.find(obj)
if (nodeExists === null) {
if (elements.some(e => e.equals(obj))) {
t.assert(false, 'tree and elements contain different results')
}
elements.push(obj)
tree.put({_id: obj})
}
} else if (elements.length > 0) {
// ~20% chance to delete an element
var elem = random.oneOf(prng, elements)
elements = elements.filter(e => {
return !e.equals(elem)
})
tree.delete(elem)
}
}
checkRootNodeIsBlack(t, tree)
checkBlackHeightOfSubTreesAreEqual(t, tree)
checkRedNodesDoNotHaveBlackChildren(t, tree)
// TEST if all nodes exist
let allNodesExist = true
for (let id of elements) {
let node = tree.find(id)
if (!node._id.equals(id)) {
allNodesExist = false
}
}
t.assert(allNodesExist, 'All inserted nodes exist')
// TEST lower bound search
let findAllNodesWithLowerBoundSerach = true
for (let id of elements) {
let node = tree.findWithLowerBound(id)
if (!node._id.equals(id)) {
findAllNodesWithLowerBoundSerach = false
}
}
t.assert(
findAllNodesWithLowerBoundSerach,
'Find every object with lower bound search'
)
// TEST iteration (with lower bound search)
let lowerBound = random.oneOf(prng, elements)
let expectedResults = elements.filter((e, pos) =>
(lowerBound.lessThan(e) || e.equals(lowerBound)) &&
elements.indexOf(e) === pos
).length
let actualResults = 0
tree.iterate(lowerBound, null, val => {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'Iterating over a tree with lower bound yields the right amount of results'
)
expectedResults = elements.filter((e, pos) =>
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(null, null, val => {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree without bounds yields the right amount of results'
)
let upperBound = random.oneOf(prng, elements)
expectedResults = elements.filter((e, pos) =>
(e.lessThan(upperBound) || e.equals(upperBound)) &&
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(null, upperBound, val => {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
upperBound = random.oneOf(prng, elements)
lowerBound = random.oneOf(prng, elements)
if (upperBound.lessThan(lowerBound)) {
[lowerBound, upperBound] = [upperBound, lowerBound]
}
expectedResults = elements.filter((e, pos) =>
(lowerBound.lessThan(e) || e.equals(lowerBound)) &&
(e.lessThan(upperBound) || e.equals(upperBound)) &&
elements.indexOf(e) === pos
).length
actualResults = 0
tree.iterate(lowerBound, upperBound, val => {
if (val == null) {
t.assert(false, 'val is undefined!')
}
actualResults++
})
t.assert(
expectedResults === actualResults,
'iterating over a tree with upper bound yields the right amount of results'
)
})

View File

@@ -1,7 +1,7 @@
import { initArrays, compareUsers, applyRandomTests } from './helper.js'
import * as Y from '../index.js'
import * as Y from '../src/index.js'
import { test, proxyConsole } from 'cutest'
import * as random from '../lib/prng/prng.js'
import * as random from 'funlib/prng/prng.js'
proxyConsole()
test('basic spec', async function array0 (t) {
@@ -213,13 +213,13 @@ test('should correctly iterate an array containing types', async function iterat
const y = new Y.Y()
const arr = y.define('arr', Y.Array)
const numItems = 10
for(let i = 0; i < numItems; i++) {
for (let i = 0; i < numItems; i++) {
const map = new Y.Map()
map.set('value', i)
arr.push([map])
}
let cnt = 0
for(let item of arr) {
for (let item of arr) {
t.assert(item.get('value') === cnt++, 'value is correct')
}
y.destroy()

View File

@@ -1,7 +1,7 @@
import { initArrays, compareUsers, applyRandomTests } from './helper.js'
import * as Y from '../index.js'
import * as Y from '../src/index.js'
import { test, proxyConsole } from 'cutest'
import * as random from '../lib/prng/prng.js'
import * as random from 'funlib/prng/prng.js'
proxyConsole()

View File

@@ -1,7 +1,5 @@
import { initArrays, compareUsers } from './helper.js'
import { test, proxyConsole } from 'cutest'
proxyConsole()
import { test } from 'cutest'
test('basic insert delete', async function text0 (t) {
let { users, text0 } = await initArrays(t, { users: 2 })
@@ -47,7 +45,7 @@ test('basic format', async function text1 (t) {
t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
t.compare(text0.toDelta(), [{ insert: 'b', attributes: { bold: true } }])
t.compare(delta, [{ retain: 1 }, { delete: 1 }])
text0.insert(0, 'z', {bold: true})
text0.insert(0, 'z', { bold: true })
t.assert(text0.toString() === 'zb')
t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }])
t.compare(delta, [{ insert: 'z', attributes: { bold: true } }])

View File

@@ -1,7 +1,6 @@
import { initArrays, compareUsers, applyRandomTests } from './helper.js'
import { initArrays, compareUsers } from './helper.js'
import { test } from 'cutest'
import * as Y from '../index.js'
import * as random from '../lib/prng/prng.js'
import * as Y from '../src/index.js'
test('set property', async function xml0 (t) {
var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 })
@@ -60,30 +59,6 @@ test('attribute modifications (y -> dom)', async function xml2 (t) {
await compareUsers(t, users)
})
test('attribute modifications (dom -> y)', async function xml3 (t) {
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, 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)
})
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')])
@@ -93,17 +68,6 @@ test('element insert (y -> dom)', async function xml5 (t) {
await compareUsers(t, users)
})
test('y on insert, then delete (dom -> y)', async function xml6 (t) {
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)
})
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')])
@@ -169,79 +133,6 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) {
await compareUsers(t, users)
})
test('move element to a different position', async function xml13 (t) {
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)')
t.assert(dom1.childNodes[1].nodeName === 'DIV', 'div was moved to the correct position (user 1)')
await compareUsers(t, users)
})
test('filter node', async function xml14 (t) {
var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 })
let domFilter = (nodeName, attrs) => {
if (nodeName === 'H1') {
return null
} else {
return attrs
}
}
domBinding0.setFilter(domFilter)
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')
await compareUsers(t, users)
})
test('filter attribute', async function xml15 (t) {
var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 })
let domFilter = (nodeName, attrs) => {
attrs.delete('hidden')
return attrs
}
domBinding0.setFilter(domFilter)
domBinding1.setFilter(domFilter)
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')
t.assert(dom1.getAttribute('style') === 'height: 30px', 'User 1 received style update')
t.assert(dom1.getAttribute('data-me') === '77', 'User 1 received data update')
await compareUsers(t, users)
})
test('deep element insert', async function xml16 (t) {
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')
attrElement.setAttribute('src', 'http:localhost:8080/nowhere')
boldElement.append(document.createTextNode('hi'))
deepElement.append(boldElement)
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')
await compareUsers(t, users)
})
test('treeWalker', async function xml17 (t) {
var { users, xml0 } = await initArrays(t, { users: 3 })
let paragraph1 = new Y.XmlElement('p')
@@ -257,160 +148,3 @@ test('treeWalker', async function xml17 (t) {
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
await compareUsers(t, users)
})
/**
* The expected behavior is that changes on your own dom (e.g. malicious attributes) persist.
* Yjs should just ignore them, never propagate those attributes.
* Incoming changes that contain malicious attributes should be deleted.
*/
test('Filtering remote changes', async function xmlFilteringRemote (t) {
var { testConnector, users, xml0, xml1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 })
const filter = (nodeName, attributes) => {
attributes.delete('malicious')
if (nodeName === 'HIDEME') {
return null
} else if (attributes.has('isHidden')) {
return null
} 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')
span.setAttribute('malicious', 'alert("give me money")')
let tag = new Y.XmlElement('tag')
tag.setAttribute('isHidden', 'true')
paragraph.insert(0, [hideMe, span, tag])
xml0.insert(0, [paragraph])
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)
})
// TODO: move elements
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) {
let dom = user.dom
if (dom.children.length > 0) {
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) {
let dom = user.dom
if (dom.children.length > 0) {
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) {
let dom = user.dom
if (dom.children.length > 0) {
let dom2 = random.oneOf(prng, dom.children)
if (dom2.childNodes.length > 0) {
let d = random.oneOf(prng, dom2.childNodes)
d.remove()
}
user.domBinding.flushDomChanges()
}
}
]
test('y-xml: Random tests (10)', async function xmlRandom10 (t) {
await applyRandomTests(t, xmlTransactions, 10)
})
test('y-xml: Random tests (42)', async function xmlRandom42 (t) {
await applyRandomTests(t, xmlTransactions, 42)
})
test('y-xml: Random tests (43)', async function xmlRandom43 (t) {
await applyRandomTests(t, xmlTransactions, 43)
})
test('y-xml: Random tests (44)', async function xmlRandom44 (t) {
await applyRandomTests(t, xmlTransactions, 44)
})
test('y-xml: Random tests (45)', async function xmlRandom45 (t) {
await applyRandomTests(t, xmlTransactions, 45)
})
test('y-xml: Random tests (46)', async function xmlRandom46 (t) {
await applyRandomTests(t, xmlTransactions, 46)
})
test('y-xml: Random tests (47)', async function xmlRandom47 (t) {
await applyRandomTests(t, xmlTransactions, 47)
})
test('y-xml: Random tests (100)', async function xmlRandom100 (t) {
await applyRandomTests(t, xmlTransactions, 100)
})
test('y-xml: Random tests (200)', async function xmlRandom200 (t) {
await applyRandomTests(t, xmlTransactions, 200)
})
test('y-xml: Random tests (500)', async function xmlRandom500 (t) {
await applyRandomTests(t, xmlTransactions, 500)
})
test('y-xml: Random tests (1000)', async function xmlRandom1000 (t) {
await applyRandomTests(t, xmlTransactions, 1000)
})