@y/y 14.0.0-16 → 14.0.0-18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{index-DyTeTfmj.js → index-BV-j5wdP.js} +2 -2
  2. package/dist/{index-R7GxO-36.js.map → index-BV-j5wdP.js.map} +1 -1
  3. package/dist/{internals.mjs → internals.js} +1 -1
  4. package/dist/internals.js.map +1 -0
  5. package/dist/{testHelper.mjs → testHelper.js} +2 -2
  6. package/dist/testHelper.js.map +1 -0
  7. package/dist/{yjs.mjs → yjs.js} +2 -2
  8. package/dist/yjs.js.map +1 -0
  9. package/package.json +9 -18
  10. package/dist/Skip-j0kX7pdq.js +0 -12173
  11. package/dist/Skip-j0kX7pdq.js.map +0 -1
  12. package/dist/index-DyTeTfmj.js.map +0 -1
  13. package/dist/index-R7GxO-36.js +0 -165
  14. package/dist/internals.cjs +0 -286
  15. package/dist/internals.cjs.map +0 -1
  16. package/dist/internals.mjs.map +0 -1
  17. package/dist/testHelper.cjs +0 -780
  18. package/dist/testHelper.cjs.map +0 -1
  19. package/dist/testHelper.mjs.map +0 -1
  20. package/dist/yjs.cjs +0 -151
  21. package/dist/yjs.cjs.map +0 -1
  22. package/dist/yjs.mjs.map +0 -1
  23. package/src/index.js +0 -153
  24. package/src/internals.js +0 -44
  25. package/src/structs/AbstractStruct.js +0 -59
  26. package/src/structs/ContentAny.js +0 -115
  27. package/src/structs/ContentBinary.js +0 -93
  28. package/src/structs/ContentDeleted.js +0 -101
  29. package/src/structs/ContentDoc.js +0 -141
  30. package/src/structs/ContentEmbed.js +0 -98
  31. package/src/structs/ContentFormat.js +0 -105
  32. package/src/structs/ContentJSON.js +0 -119
  33. package/src/structs/ContentString.js +0 -113
  34. package/src/structs/ContentType.js +0 -176
  35. package/src/structs/GC.js +0 -80
  36. package/src/structs/Item.js +0 -845
  37. package/src/structs/Skip.js +0 -75
  38. package/src/types/AbstractType.js +0 -1434
  39. package/src/types/YArray.js +0 -270
  40. package/src/types/YMap.js +0 -244
  41. package/src/types/YText.js +0 -934
  42. package/src/types/YXmlElement.js +0 -227
  43. package/src/types/YXmlFragment.js +0 -266
  44. package/src/types/YXmlHook.js +0 -68
  45. package/src/types/YXmlText.js +0 -66
  46. package/src/utils/AbstractConnector.js +0 -25
  47. package/src/utils/AttributionManager.js +0 -619
  48. package/src/utils/Doc.js +0 -372
  49. package/src/utils/EventHandler.js +0 -87
  50. package/src/utils/ID.js +0 -89
  51. package/src/utils/IdMap.js +0 -629
  52. package/src/utils/IdSet.js +0 -823
  53. package/src/utils/RelativePosition.js +0 -352
  54. package/src/utils/Snapshot.js +0 -220
  55. package/src/utils/StructSet.js +0 -137
  56. package/src/utils/StructStore.js +0 -289
  57. package/src/utils/Transaction.js +0 -489
  58. package/src/utils/UndoManager.js +0 -391
  59. package/src/utils/UpdateDecoder.js +0 -281
  60. package/src/utils/UpdateEncoder.js +0 -320
  61. package/src/utils/YEvent.js +0 -216
  62. package/src/utils/delta-helpers.js +0 -54
  63. package/src/utils/encoding.js +0 -623
  64. package/src/utils/isParentOf.js +0 -21
  65. package/src/utils/logging.js +0 -21
  66. package/src/utils/types.js +0 -28
  67. package/src/utils/updates.js +0 -715
  68. package/tests/testHelper.js +0 -600
@@ -1,600 +0,0 @@
1
- import * as t from 'lib0/testing'
2
- import * as prng from 'lib0/prng'
3
- import * as encoding from 'lib0/encoding'
4
- import * as decoding from 'lib0/decoding'
5
- import * as syncProtocol from '@y/protocols/sync'
6
- import * as object from 'lib0/object'
7
- import * as map from 'lib0/map'
8
- import * as Y from '../src/index.js'
9
- import * as math from 'lib0/math'
10
- import * as list from 'lib0/list'
11
- import * as delta from 'lib0/delta'
12
- import {
13
- createIdSet, createIdMap, addToIdSet, encodeIdMap
14
- } from '../src/internals.js'
15
-
16
- export * from '../src/index.js'
17
-
18
- if (typeof window !== 'undefined') {
19
- // @ts-ignore
20
- window.Y = Y // eslint-disable-line
21
- }
22
-
23
- /**
24
- * @param {TestYInstance} y // publish message created by `y` to all other online clients
25
- * @param {Uint8Array} m
26
- */
27
- const broadcastMessage = (y, m) => {
28
- if (y.tc.onlineConns.has(y)) {
29
- y.tc.onlineConns.forEach(remoteYInstance => {
30
- if (remoteYInstance !== y) {
31
- remoteYInstance._receive(m, y)
32
- }
33
- })
34
- }
35
- }
36
-
37
- export let useV2 = false
38
-
39
- export const encV1 = {
40
- encodeStateAsUpdate: Y.encodeStateAsUpdate,
41
- mergeUpdates: Y.mergeUpdates,
42
- applyUpdate: Y.applyUpdate,
43
- logUpdate: Y.logUpdate,
44
- updateEventName: /** @type {'update'} */ ('update'),
45
- diffUpdate: Y.diffUpdate
46
- }
47
-
48
- export const encV2 = {
49
- encodeStateAsUpdate: Y.encodeStateAsUpdateV2,
50
- mergeUpdates: Y.mergeUpdatesV2,
51
- applyUpdate: Y.applyUpdateV2,
52
- logUpdate: Y.logUpdateV2,
53
- updateEventName: /** @type {'updateV2'} */ ('updateV2'),
54
- diffUpdate: Y.diffUpdateV2
55
- }
56
-
57
- export let enc = encV1
58
-
59
- const useV1Encoding = () => {
60
- useV2 = false
61
- enc = encV1
62
- }
63
-
64
- const useV2Encoding = () => {
65
- console.error('sync protocol doesnt support v2 protocol yet, fallback to v1 encoding') // @Todo
66
- useV2 = false
67
- enc = encV1
68
- }
69
-
70
- export class TestYInstance extends Y.Doc {
71
- /**
72
- * @param {TestConnector} testConnector
73
- * @param {number} clientID
74
- */
75
- constructor (testConnector, clientID) {
76
- super()
77
- this.userID = clientID // overwriting clientID
78
- /**
79
- * @type {TestConnector}
80
- */
81
- this.tc = testConnector
82
- /**
83
- * @type {Map<TestYInstance, Array<Uint8Array>>}
84
- */
85
- this.receiving = new Map()
86
- testConnector.allConns.add(this)
87
- /**
88
- * The list of received updates.
89
- * We are going to merge them later using Y.mergeUpdates and check if the resulting document is correct.
90
- * @type {Array<Uint8Array<ArrayBuffer>>}
91
- */
92
- this.updates = []
93
- // set up observe on local model
94
- this.on(enc.updateEventName, (update, origin) => {
95
- if (origin !== testConnector) {
96
- const encoder = encoding.createEncoder()
97
- syncProtocol.writeUpdate(encoder, update)
98
- broadcastMessage(this, encoding.toUint8Array(encoder))
99
- }
100
- this.updates.push(update)
101
- })
102
- this.connect()
103
- }
104
-
105
- /**
106
- * Disconnect from TestConnector.
107
- */
108
- disconnect () {
109
- this.receiving = new Map()
110
- this.tc.onlineConns.delete(this)
111
- }
112
-
113
- /**
114
- * Append yourself to the list of known Y instances in testconnector.
115
- * Also initiate sync with all clients.
116
- */
117
- connect () {
118
- if (!this.tc.onlineConns.has(this)) {
119
- this.tc.onlineConns.add(this)
120
- const encoder = encoding.createEncoder()
121
- syncProtocol.writeSyncStep1(encoder, this)
122
- // publish SyncStep1
123
- broadcastMessage(this, encoding.toUint8Array(encoder))
124
- this.tc.onlineConns.forEach(remoteYInstance => {
125
- if (remoteYInstance !== this) {
126
- // remote instance sends instance to this instance
127
- const encoder = encoding.createEncoder()
128
- syncProtocol.writeSyncStep1(encoder, remoteYInstance)
129
- this._receive(encoding.toUint8Array(encoder), remoteYInstance)
130
- }
131
- })
132
- }
133
- }
134
-
135
- /**
136
- * Receive a message from another client. This message is only appended to the list of receiving messages.
137
- * TestConnector decides when this client actually reads this message.
138
- *
139
- * @param {Uint8Array} message
140
- * @param {TestYInstance} remoteClient
141
- */
142
- _receive (message, remoteClient) {
143
- map.setIfUndefined(this.receiving, remoteClient, () => /** @type {Array<Uint8Array>} */ ([])).push(message)
144
- }
145
- }
146
-
147
- /**
148
- * Keeps track of TestYInstances.
149
- *
150
- * The TestYInstances add/remove themselves from the list of connections maiained in this object.
151
- * I think it makes sense. Deal with it.
152
- */
153
- export class TestConnector {
154
- /**
155
- * @param {prng.PRNG} gen
156
- */
157
- constructor (gen) {
158
- /**
159
- * @type {Set<TestYInstance>}
160
- */
161
- this.allConns = new Set()
162
- /**
163
- * @type {Set<TestYInstance>}
164
- */
165
- this.onlineConns = new Set()
166
- /**
167
- * @type {prng.PRNG}
168
- */
169
- this.prng = gen
170
- }
171
-
172
- /**
173
- * Create a new Y instance and add it to the list of connections
174
- * @param {number} clientID
175
- */
176
- createY (clientID) {
177
- return new TestYInstance(this, clientID)
178
- }
179
-
180
- /**
181
- * Choose random connection and flush a random message from a random sender.
182
- *
183
- * If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise.
184
- * @return {boolean}
185
- */
186
- flushRandomMessage () {
187
- const gen = this.prng
188
- const conns = Array.from(this.onlineConns).filter(conn => conn.receiving.size > 0)
189
- if (conns.length > 0) {
190
- const receiver = prng.oneOf(gen, conns)
191
- const [sender, messages] = prng.oneOf(gen, Array.from(receiver.receiving))
192
- const m = messages.shift()
193
- if (messages.length === 0) {
194
- receiver.receiving.delete(sender)
195
- }
196
- if (m === undefined) {
197
- return this.flushRandomMessage()
198
- }
199
- const encoder = encoding.createEncoder()
200
- // console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver))
201
- // do not publish data created when this function is executed (could be ss2 or update message)
202
- syncProtocol.readSyncMessage(decoding.createDecoder(m), encoder, receiver, receiver.tc)
203
- if (encoding.length(encoder) > 0) {
204
- // send reply message
205
- sender._receive(encoding.toUint8Array(encoder), receiver)
206
- }
207
- return true
208
- }
209
- return false
210
- }
211
-
212
- /**
213
- * @return {boolean} True iff this function actually flushed something
214
- */
215
- flushAllMessages () {
216
- let didSomething = false
217
- while (this.flushRandomMessage()) {
218
- didSomething = true
219
- }
220
- return didSomething
221
- }
222
-
223
- reconnectAll () {
224
- this.allConns.forEach(conn => conn.connect())
225
- }
226
-
227
- disconnectAll () {
228
- this.allConns.forEach(conn => conn.disconnect())
229
- }
230
-
231
- syncAll () {
232
- this.reconnectAll()
233
- this.flushAllMessages()
234
- }
235
-
236
- /**
237
- * @return {boolean} Whether it was possible to disconnect a random connection.
238
- */
239
- disconnectRandom () {
240
- if (this.onlineConns.size === 0) {
241
- return false
242
- }
243
- prng.oneOf(this.prng, Array.from(this.onlineConns)).disconnect()
244
- return true
245
- }
246
-
247
- /**
248
- * @return {boolean} Whether it was possible to reconnect a random connection.
249
- */
250
- reconnectRandom () {
251
- /**
252
- * @type {Array<TestYInstance>}
253
- */
254
- const reconnectable = []
255
- this.allConns.forEach(conn => {
256
- if (!this.onlineConns.has(conn)) {
257
- reconnectable.push(conn)
258
- }
259
- })
260
- if (reconnectable.length === 0) {
261
- return false
262
- }
263
- prng.oneOf(this.prng, reconnectable).connect()
264
- return true
265
- }
266
- }
267
-
268
- /**
269
- * @template T
270
- * @param {t.TestCase} tc
271
- * @param {{users?:number}} conf
272
- * @param {InitTestObjectCallback<T>} [initTestObject]
273
- * @return {{testObjects:Array<any>,testConnector:TestConnector,users:Array<TestYInstance>,array0:Y.Array<any>,array1:Y.Array<any>,array2:Y.Array<any>,map0:Y.Map<any>,map1:Y.Map<any>,map2:Y.Map<any>,map3:Y.Map<any>,text0:Y.Text,text1:Y.Text,text2:Y.Text,xml0:Y.XmlElement,xml1:Y.XmlElement,xml2:Y.XmlElement}}
274
- */
275
- export const init = (tc, { users = 5 } = {}, initTestObject) => {
276
- /**
277
- * @type {Object<string,any>}
278
- */
279
- const result = {
280
- users: []
281
- }
282
- const gen = tc.prng
283
- // choose an encoding approach at random
284
- if (prng.bool(gen)) {
285
- useV2Encoding()
286
- } else {
287
- useV1Encoding()
288
- }
289
-
290
- const testConnector = new TestConnector(gen)
291
- result.testConnector = testConnector
292
- for (let i = 0; i < users; i++) {
293
- const y = testConnector.createY(i)
294
- y.clientID = i
295
- result.users.push(y)
296
- result['array' + i] = y.getArray('array')
297
- result['map' + i] = y.getMap('map')
298
- result['xml' + i] = y.get('xml', Y.XmlElement)
299
- result['text' + i] = y.getText('text')
300
- }
301
- testConnector.syncAll()
302
- result.testObjects = result.users.map(initTestObject || (() => null))
303
- useV1Encoding()
304
- return /** @type {any} */ (result)
305
- }
306
-
307
- /**
308
- * @param {Y.IdSet} idSet1
309
- * @param {Y.IdSet} idSet2
310
- */
311
- export const compareIdSets = (idSet1, idSet2) => {
312
- t.assert(idSet1.clients.size === idSet2.clients.size)
313
- for (const [client, _items1] of idSet1.clients.entries()) {
314
- const items1 = _items1.getIds()
315
- const items2 = idSet2.clients.get(client)?.getIds()
316
- t.assert(items2 !== undefined && items1.length === items2.length)
317
- for (let i = 0; i < items1.length; i++) {
318
- const di1 = items1[i]
319
- const di2 = /** @type {Array<import('../src/utils/IdSet.js').IdRange>} */ (items2)[i]
320
- t.assert(di1.clock === di2.clock && di1.len === di2.len)
321
- }
322
- }
323
- return true
324
- }
325
-
326
- /**
327
- * only use for testing
328
- *
329
- * @template T
330
- * @param {Array<Y.Attribution<T>>} attrs
331
- * @param {Y.Attribution<T>} attr
332
- *
333
- */
334
- const _idmapAttrsHas = (attrs, attr) => {
335
- const hash = attr.hash()
336
- return attrs.find(a => a.hash() === hash)
337
- }
338
-
339
- /**
340
- * only use for testing
341
- *
342
- * @template T
343
- * @param {Array<Y.Attribution<T>>} a
344
- * @param {Array<Y.Attribution<T>>} b
345
- */
346
- export const _idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => _idmapAttrsHas(b, v))
347
-
348
- /**
349
- * Ensure that all attributes exist. Also create a copy and compare it to the original.
350
- *
351
- * @template T
352
- * @param {Y.IdMap<T>} idmap
353
- */
354
- export const validateIdMap = idmap => {
355
- const copy = Y.createIdMap()
356
- idmap.clients.forEach((ranges, client) => {
357
- ranges.getIds().forEach(range => {
358
- range.attrs.forEach(attr => {
359
- t.assert(idmap.attrs.has(attr))
360
- t.assert(idmap.attrsH.get(attr.hash()) === attr)
361
- copy.add(client, range.clock, range.len, range.attrs.slice())
362
- })
363
- })
364
- t.assert(copy.clients.get(client)?.getIds().length === ranges.getIds().length)
365
- })
366
- t.assert(idmap.attrsH.size === idmap.attrs.size)
367
- }
368
-
369
- /**
370
- * @template T
371
- * @param {Y.IdMap<T>} idmap1
372
- * @param {Y.IdMap<T>} idmap2
373
- */
374
- export const compareIdmaps = (idmap1, idmap2) => {
375
- t.assert(idmap1.clients.size === idmap2.clients.size)
376
- for (const [client, _items1] of idmap1.clients.entries()) {
377
- const items1 = _items1.getIds()
378
- const items2 = idmap2.clients.get(client)?.getIds()
379
- t.assert(items2 !== undefined && items1.length === items2.length)
380
- for (let i = 0; i < items1.length; i++) {
381
- const di1 = items1[i]
382
- const di2 = /** @type {Array<import('../src/utils/IdMap.js').AttrRange<T>>} */ (items2)[i]
383
- t.assert(di1.clock === di2.clock && di1.len === di2.len && _idmapAttrsEqual(di1.attrs, di2.attrs))
384
- }
385
- }
386
- validateIdMap(idmap1)
387
- validateIdMap(idmap2)
388
- }
389
-
390
- /**
391
- * @param {prng.PRNG} gen
392
- * @param {number} clients
393
- * @param {number} clockRange (max clock - exclusive - by each client)
394
- */
395
- export const createRandomIdSet = (gen, clients, clockRange) => {
396
- const maxOpLen = 5
397
- const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
398
- const idset = createIdSet()
399
- for (let i = 0; i < numOfOps; i++) {
400
- const client = prng.uint32(gen, 0, clients - 1)
401
- const clockStart = prng.uint32(gen, 0, clockRange)
402
- const len = prng.uint32(gen, 0, clockRange - clockStart)
403
- addToIdSet(idset, client, clockStart, len)
404
- }
405
- if (idset.clients.size === clients && clients > 1 && prng.bool(gen)) {
406
- idset.clients.delete(prng.uint32(gen, 0, clients))
407
- }
408
- return idset
409
- }
410
-
411
- /**
412
- * @template T
413
- * @param {prng.PRNG} gen
414
- * @param {number} clients
415
- * @param {number} clockRange (max clock - exclusive - by each client)
416
- * @param {Array<T>} attrChoices (max clock - exclusive - by each client)
417
- * @return {Y.IdMap<T>}
418
- */
419
- export const createRandomIdMap = (gen, clients, clockRange, attrChoices) => {
420
- const maxOpLen = 5
421
- const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
422
- const idMap = createIdMap()
423
- for (let i = 0; i < numOfOps; i++) {
424
- const client = prng.uint32(gen, 0, clients - 1)
425
- const clockStart = prng.uint32(gen, 0, clockRange)
426
- const len = prng.uint32(gen, 0, clockRange - clockStart)
427
- const attrs = [prng.oneOf(gen, attrChoices)]
428
- // maybe add another attr
429
- if (prng.bool(gen)) {
430
- const a = prng.oneOf(gen, attrChoices)
431
- if (attrs.find(attr => attr === a) == null) {
432
- attrs.push(a)
433
- }
434
- }
435
- idMap.add(client, clockStart, len, attrs.map(v => Y.createAttributionItem('', v)))
436
- }
437
- t.info(`Created IdMap with ${numOfOps} ranges and ${attrChoices.length} different attributes. Encoded size: ${encodeIdMap(idMap).byteLength}`)
438
- return idMap
439
- }
440
-
441
- /**
442
- * 1. reconnect and flush all
443
- * 2. user 0 gc
444
- * 3. get type content
445
- * 4. disconnect & reconnect all (so gc is propagated)
446
- * 5. compare os, ds, ss
447
- *
448
- * @param {Array<TestYInstance>} users
449
- */
450
- export const compare = users => {
451
- users.forEach(u => u.connect())
452
- while (users[0].tc.flushAllMessages()) {} // eslint-disable-line
453
- // For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users"
454
- // This ensures that mergeUpdates works correctly
455
- const mergedDocs = users.map(user => {
456
- const ydoc = new Y.Doc()
457
- enc.applyUpdate(ydoc, enc.mergeUpdates(user.updates))
458
- return ydoc
459
- })
460
- users.push(.../** @type {any} */(mergedDocs))
461
- const userArrayValues = users.map(u => u.getArray('array').toJSON())
462
- const userMapValues = users.map(u => u.getMap('map').toJSON())
463
- // @todo fix type error here
464
- // @ts-ignore
465
- const userXmlValues = users.map(u => /** @type {Y.XmlElement} */ (u.get('xml', Y.XmlElement)).toString())
466
- const userTextValues = users.map(u => u.getText('text').getContentDeep())
467
- for (const u of users) {
468
- t.assert(u.store.pendingDs === null)
469
- t.assert(u.store.pendingStructs === null)
470
- }
471
- // Test Array iterator
472
- t.compare(users[0].getArray('array').toArray(), Array.from(users[0].getArray('array')))
473
- // Test Map iterator
474
- const ymapkeys = Array.from(users[0].getMap('map').keys())
475
- t.assert(ymapkeys.length === Object.keys(userMapValues[0]).length)
476
- ymapkeys.forEach(key => t.assert(object.hasProperty(userMapValues[0], key)))
477
- /**
478
- * @type {Object<string,any>}
479
- */
480
- const mapRes = {}
481
- for (const [k, v] of users[0].getMap('map')) {
482
- mapRes[k] = v instanceof Y.AbstractType ? v.toJSON() : v
483
- }
484
- t.compare(userMapValues[0], mapRes)
485
- // Compare all users
486
- for (let i = 0; i < users.length - 1; i++) {
487
- t.compare(userArrayValues[i].length, users[i].getArray('array').length)
488
- t.compare(userArrayValues[i], userArrayValues[i + 1])
489
- t.compare(userMapValues[i], userMapValues[i + 1])
490
- t.compare(userXmlValues[i], userXmlValues[i + 1])
491
- t.compare(list.toArray(userTextValues[i].children).map(a => (delta.$textOp.check(a) || delta.$insertOp.check(a)) ? a.insert.length : 0).reduce((a, b) => a + b, 0), users[i].getText('text').length)
492
- t.compare(userTextValues[i], userTextValues[i + 1], '', (_constructor, a, b) => {
493
- if (a instanceof Y.AbstractType) {
494
- t.compare(a.toJSON(), b.toJSON())
495
- } else if (a !== b) {
496
- t.fail('Deltas dont match')
497
- }
498
- return true
499
- })
500
- t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1]))
501
- Y.equalIdSets(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store))
502
- compareStructStores(users[i].store, users[i + 1].store)
503
- t.compare(Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1])))
504
- }
505
- users.forEach(user => {
506
- compareIdSets(user.store.ds, Y.createDeleteSetFromStructStore(user.store))
507
- })
508
- users.map(u => u.destroy())
509
- }
510
-
511
- /**
512
- * @param {Y.Item?} a
513
- * @param {Y.Item?} b
514
- * @return {boolean}
515
- */
516
- export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id))
517
-
518
- /**
519
- * @param {import('../src/internals.js').StructStore} ss1
520
- * @param {import('../src/internals.js').StructStore} ss2
521
- */
522
- export const compareStructStores = (ss1, ss2) => {
523
- t.assert(ss1.clients.size === ss2.clients.size)
524
- for (const [client, structs1] of ss1.clients) {
525
- const structs2 = /** @type {Array<Y.AbstractStruct>} */ (ss2.clients.get(client))
526
- t.assert(structs2 !== undefined && structs1.length === structs2.length)
527
- for (let i = 0; i < structs1.length; i++) {
528
- const s1 = structs1[i]
529
- const s2 = structs2[i]
530
- // checks for abstract struct
531
- if (
532
- s1.constructor !== s2.constructor ||
533
- !Y.compareIDs(s1.id, s2.id) ||
534
- s1.deleted !== s2.deleted ||
535
- // @ts-ignore
536
- s1.length !== s2.length
537
- ) {
538
- t.fail('Structs dont match')
539
- }
540
- if (s1 instanceof Y.Item) {
541
- if (
542
- !(s2 instanceof Y.Item) ||
543
- !((s1.left === null && s2.left === null) || (s1.left !== null && s2.left !== null && Y.compareIDs(s1.left.lastId, s2.left.lastId))) ||
544
- !compareItemIDs(s1.right, s2.right) ||
545
- !Y.compareIDs(s1.origin, s2.origin) ||
546
- !Y.compareIDs(s1.rightOrigin, s2.rightOrigin) ||
547
- s1.parentSub !== s2.parentSub
548
- ) {
549
- return t.fail('Items dont match')
550
- }
551
- // make sure that items are connected correctly
552
- t.assert(s1.left === null || s1.left.right === s1)
553
- t.assert(s1.right === null || s1.right.left === s1)
554
- t.assert(s2.left === null || s2.left.right === s2)
555
- t.assert(s2.right === null || s2.right.left === s2)
556
- }
557
- }
558
- }
559
- }
560
-
561
- /**
562
- * @template T
563
- * @callback InitTestObjectCallback
564
- * @param {TestYInstance} y
565
- * @return {T}
566
- */
567
-
568
- /**
569
- * @template T
570
- * @param {t.TestCase} tc
571
- * @param {Array<function(Y.Doc,prng.PRNG,T):void>} mods
572
- * @param {number} iterations
573
- * @param {InitTestObjectCallback<T>} [initTestObject]
574
- */
575
- export const applyRandomTests = (tc, mods, iterations, initTestObject) => {
576
- const gen = tc.prng
577
- const result = init(tc, { users: 5 }, initTestObject)
578
- const { testConnector, users } = result
579
- for (let i = 0; i < iterations; i++) {
580
- if (prng.int32(gen, 0, 100) <= 2) {
581
- // 2% chance to disconnect/reconnect a random user
582
- if (prng.bool(gen)) {
583
- testConnector.disconnectRandom()
584
- } else {
585
- testConnector.reconnectRandom()
586
- }
587
- } else if (prng.int32(gen, 0, 100) <= 1) {
588
- // 1% chance to flush all
589
- testConnector.flushAllMessages()
590
- } else if (prng.int32(gen, 0, 100) <= 50) {
591
- // 50% chance to flush a random message
592
- testConnector.flushRandomMessage()
593
- }
594
- const user = prng.int32(gen, 0, users.length - 1)
595
- const test = prng.oneOf(gen, mods)
596
- test(users[user], gen, result.testObjects[user])
597
- }
598
- compare(users)
599
- return result
600
- }