@kyneta/yjs-schema 1.0.0 → 1.2.0
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.
- package/dist/index.d.ts +109 -147
- package/dist/index.js +321 -210
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/__tests__/bind-constraints.test.ts +333 -0
- package/src/__tests__/bind-yjs.test.ts +53 -55
- package/src/__tests__/create.test.ts +71 -62
- package/src/__tests__/{store-reader.test.ts → reader.test.ts} +64 -90
- package/src/__tests__/record-text-spike.test.ts +38 -31
- package/src/__tests__/structural-merge.test.ts +362 -0
- package/src/__tests__/substrate.test.ts +65 -84
- package/src/__tests__/version.test.ts +82 -16
- package/src/bind-yjs.ts +115 -64
- package/src/change-mapping.ts +60 -84
- package/src/create.ts +33 -28
- package/src/index.ts +32 -51
- package/src/populate.ts +87 -92
- package/src/{store-reader.ts → reader.ts} +7 -12
- package/src/substrate.ts +186 -42
- package/src/sync.ts +26 -26
- package/src/version.ts +57 -4
- package/src/yjs-resolve.ts +5 -21
- package/src/yjs-escape.ts +0 -100
|
@@ -1,34 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { change, RawPath, Schema, subscribe } from "@kyneta/schema"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
2
3
|
import * as Y from "yjs"
|
|
3
|
-
import {
|
|
4
|
-
import { createYjsSubstrate, yjsSubstrateFactory } from "../substrate.js"
|
|
5
|
-
import { YjsVersion } from "../version.js"
|
|
6
|
-
import { createYjsDoc, createYjsDocFromSnapshot } from "../create.js"
|
|
7
|
-
import {
|
|
8
|
-
version,
|
|
9
|
-
exportSnapshot,
|
|
10
|
-
exportSince,
|
|
11
|
-
importDelta,
|
|
12
|
-
} from "../sync.js"
|
|
4
|
+
import { createYjsDoc, createYjsDocFromEntirety } from "../create.js"
|
|
13
5
|
import { ensureContainers } from "../populate.js"
|
|
6
|
+
import { yjsSubstrateFactory } from "../substrate.js"
|
|
7
|
+
import { exportEntirety, exportSince, merge, version } from "../sync.js"
|
|
8
|
+
import { YjsVersion } from "../version.js"
|
|
14
9
|
|
|
15
10
|
// ===========================================================================
|
|
16
11
|
// Helpers
|
|
17
12
|
// ===========================================================================
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
14
|
// ===========================================================================
|
|
22
15
|
// Schemas used across tests
|
|
23
16
|
// ===========================================================================
|
|
24
17
|
|
|
25
|
-
const SimpleSchema = Schema.
|
|
26
|
-
title: Schema.
|
|
18
|
+
const SimpleSchema = Schema.struct({
|
|
19
|
+
title: Schema.text(),
|
|
27
20
|
count: Schema.number(),
|
|
28
21
|
items: Schema.list(Schema.string()),
|
|
29
22
|
})
|
|
30
23
|
|
|
31
|
-
const StructListSchema = Schema.
|
|
24
|
+
const StructListSchema = Schema.struct({
|
|
32
25
|
tasks: Schema.list(
|
|
33
26
|
Schema.struct({
|
|
34
27
|
name: Schema.string(),
|
|
@@ -37,8 +30,8 @@ const StructListSchema = Schema.doc({
|
|
|
37
30
|
),
|
|
38
31
|
})
|
|
39
32
|
|
|
40
|
-
const FullSchema = Schema.
|
|
41
|
-
title: Schema.
|
|
33
|
+
const FullSchema = Schema.struct({
|
|
34
|
+
title: Schema.text(),
|
|
42
35
|
count: Schema.number(),
|
|
43
36
|
active: Schema.boolean(),
|
|
44
37
|
items: Schema.list(Schema.string()),
|
|
@@ -66,10 +59,10 @@ describe("YjsSubstrate", () => {
|
|
|
66
59
|
describe("factory create", () => {
|
|
67
60
|
it("creates a substrate with empty containers", () => {
|
|
68
61
|
const substrate = yjsSubstrateFactory.create(SimpleSchema)
|
|
69
|
-
expect(substrate.
|
|
62
|
+
expect(substrate.reader.read(RawPath.empty.field("title"))).toBe("")
|
|
70
63
|
// Plain scalars return structural zeros
|
|
71
|
-
expect(substrate.
|
|
72
|
-
expect(substrate.
|
|
64
|
+
expect(substrate.reader.read(RawPath.empty.field("count"))).toBe(0)
|
|
65
|
+
expect(substrate.reader.read(RawPath.empty.field("items"))).toEqual([])
|
|
73
66
|
})
|
|
74
67
|
|
|
75
68
|
it("creates a substrate and populates via change()", () => {
|
|
@@ -188,8 +181,10 @@ describe("YjsSubstrate", () => {
|
|
|
188
181
|
describe("export/import snapshot", () => {
|
|
189
182
|
it("exports a binary payload", () => {
|
|
190
183
|
const doc = createYjsDoc(SimpleSchema)
|
|
191
|
-
change(doc, (d: any) => {
|
|
192
|
-
|
|
184
|
+
change(doc, (d: any) => {
|
|
185
|
+
d.title.insert(0, "Snapshot")
|
|
186
|
+
})
|
|
187
|
+
const payload = exportEntirety(doc)
|
|
193
188
|
expect(payload.encoding).toBe("binary")
|
|
194
189
|
expect(payload.data).toBeInstanceOf(Uint8Array)
|
|
195
190
|
})
|
|
@@ -207,8 +202,8 @@ describe("YjsSubstrate", () => {
|
|
|
207
202
|
d.title.insert(5, " World")
|
|
208
203
|
})
|
|
209
204
|
|
|
210
|
-
const payload =
|
|
211
|
-
const doc2 =
|
|
205
|
+
const payload = exportEntirety(doc1)
|
|
206
|
+
const doc2 = createYjsDocFromEntirety(SimpleSchema, payload)
|
|
212
207
|
|
|
213
208
|
expect(doc2.title()).toBe("Hello World")
|
|
214
209
|
expect(doc2.count()).toBe(42)
|
|
@@ -221,13 +216,12 @@ describe("YjsSubstrate", () => {
|
|
|
221
216
|
// -------------------------------------------------------------------------
|
|
222
217
|
|
|
223
218
|
describe("delta sync", () => {
|
|
224
|
-
it("exportSince →
|
|
219
|
+
it("exportSince → merge syncs state", () => {
|
|
225
220
|
const doc1 = createYjsDoc(SimpleSchema)
|
|
226
|
-
change(doc1, (d: any) => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
221
|
+
change(doc1, (d: any) => {
|
|
222
|
+
d.title.insert(0, "Start")
|
|
223
|
+
})
|
|
224
|
+
const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
|
|
231
225
|
|
|
232
226
|
const v1Before = version(doc1)
|
|
233
227
|
|
|
@@ -239,17 +233,14 @@ describe("YjsSubstrate", () => {
|
|
|
239
233
|
const delta = exportSince(doc1, v1Before)
|
|
240
234
|
expect(delta).not.toBeNull()
|
|
241
235
|
|
|
242
|
-
|
|
236
|
+
merge(doc2, delta!)
|
|
243
237
|
expect(doc2.title()).toBe("Start Edited")
|
|
244
238
|
expect(doc2.count()).toBe(99)
|
|
245
239
|
})
|
|
246
240
|
|
|
247
241
|
it("concurrent sync — two substrates converge after bidirectional sync", () => {
|
|
248
242
|
const doc1 = createYjsDoc(SimpleSchema)
|
|
249
|
-
const doc2 =
|
|
250
|
-
SimpleSchema,
|
|
251
|
-
exportSnapshot(doc1),
|
|
252
|
-
)
|
|
243
|
+
const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
|
|
253
244
|
|
|
254
245
|
const v1Before = version(doc1)
|
|
255
246
|
const v2Before = version(doc2)
|
|
@@ -271,8 +262,8 @@ describe("YjsSubstrate", () => {
|
|
|
271
262
|
const d1to2 = exportSince(doc1, v2Before)
|
|
272
263
|
const d2to1 = exportSince(doc2, v1Before)
|
|
273
264
|
|
|
274
|
-
|
|
275
|
-
|
|
265
|
+
merge(doc2, d1to2!)
|
|
266
|
+
merge(doc1, d2to1!)
|
|
276
267
|
|
|
277
268
|
// Should now be equal
|
|
278
269
|
expect(version(doc1).compare(version(doc2))).toBe("equal")
|
|
@@ -293,13 +284,12 @@ describe("YjsSubstrate", () => {
|
|
|
293
284
|
// -------------------------------------------------------------------------
|
|
294
285
|
|
|
295
286
|
describe("changefeed", () => {
|
|
296
|
-
it("fires on
|
|
287
|
+
it("fires on merge", () => {
|
|
297
288
|
const doc1 = createYjsDoc(SimpleSchema)
|
|
298
|
-
change(doc1, (d: any) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
)
|
|
289
|
+
change(doc1, (d: any) => {
|
|
290
|
+
d.title.insert(0, "A")
|
|
291
|
+
})
|
|
292
|
+
const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
|
|
303
293
|
|
|
304
294
|
const v2Before = version(doc2)
|
|
305
295
|
|
|
@@ -313,7 +303,7 @@ describe("YjsSubstrate", () => {
|
|
|
313
303
|
})
|
|
314
304
|
|
|
315
305
|
const delta = exportSince(doc1, v2Before)
|
|
316
|
-
|
|
306
|
+
merge(doc2, delta!)
|
|
317
307
|
|
|
318
308
|
expect(received.length).toBeGreaterThanOrEqual(1)
|
|
319
309
|
expect(doc2.count()).toBe(42)
|
|
@@ -354,19 +344,19 @@ describe("YjsSubstrate", () => {
|
|
|
354
344
|
expect(received.length).toBe(1)
|
|
355
345
|
})
|
|
356
346
|
|
|
357
|
-
it("nested struct field changefeed fires on
|
|
347
|
+
it("nested struct field changefeed fires on merge", () => {
|
|
358
348
|
const doc1 = createYjsDoc(StructListSchema)
|
|
359
|
-
const
|
|
349
|
+
const _doc2 = createYjsDocFromEntirety(
|
|
360
350
|
StructListSchema,
|
|
361
|
-
|
|
351
|
+
exportEntirety(doc1),
|
|
362
352
|
)
|
|
363
353
|
|
|
364
354
|
// Add a struct item on doc1, sync to doc2
|
|
365
355
|
change(doc1, (d: any) => {
|
|
366
356
|
d.tasks.push({ name: "Buy milk", done: false })
|
|
367
357
|
})
|
|
368
|
-
const snap =
|
|
369
|
-
const doc2b =
|
|
358
|
+
const snap = exportEntirety(doc1)
|
|
359
|
+
const doc2b = createYjsDocFromEntirety(StructListSchema, snap)
|
|
370
360
|
|
|
371
361
|
const taskB = [...doc2b.tasks][0] as any
|
|
372
362
|
expect(taskB.done()).toBe(false)
|
|
@@ -385,7 +375,7 @@ describe("YjsSubstrate", () => {
|
|
|
385
375
|
|
|
386
376
|
// Sync the toggle to doc2b
|
|
387
377
|
const delta = exportSince(doc1, v2)!
|
|
388
|
-
|
|
378
|
+
merge(doc2b, delta)
|
|
389
379
|
|
|
390
380
|
// Value should be updated
|
|
391
381
|
expect(taskB.done()).toBe(true)
|
|
@@ -396,16 +386,16 @@ describe("YjsSubstrate", () => {
|
|
|
396
386
|
unsub()
|
|
397
387
|
})
|
|
398
388
|
|
|
399
|
-
it("multi-key struct update fires per-field changefeeds on
|
|
389
|
+
it("multi-key struct update fires per-field changefeeds on merge", () => {
|
|
400
390
|
const doc1 = createYjsDoc(StructListSchema)
|
|
401
391
|
|
|
402
392
|
// Add a struct item, sync to doc2
|
|
403
393
|
change(doc1, (d: any) => {
|
|
404
394
|
d.tasks.push({ name: "Buy milk", done: false })
|
|
405
395
|
})
|
|
406
|
-
const doc2 =
|
|
396
|
+
const doc2 = createYjsDocFromEntirety(
|
|
407
397
|
StructListSchema,
|
|
408
|
-
|
|
398
|
+
exportEntirety(doc1),
|
|
409
399
|
)
|
|
410
400
|
|
|
411
401
|
const taskB = [...doc2.tasks][0] as any
|
|
@@ -428,7 +418,7 @@ describe("YjsSubstrate", () => {
|
|
|
428
418
|
|
|
429
419
|
// Sync to doc2
|
|
430
420
|
const delta = exportSince(doc1, v2)!
|
|
431
|
-
|
|
421
|
+
merge(doc2, delta)
|
|
432
422
|
|
|
433
423
|
// Both field-level changefeeds should have fired
|
|
434
424
|
expect(nameChanges.length).toBeGreaterThanOrEqual(1)
|
|
@@ -522,50 +512,41 @@ describe("YjsSubstrate", () => {
|
|
|
522
512
|
// Counter annotation throws
|
|
523
513
|
// -------------------------------------------------------------------------
|
|
524
514
|
|
|
525
|
-
describe("unsupported
|
|
526
|
-
it("counter
|
|
527
|
-
const CounterSchema = Schema.
|
|
528
|
-
count: Schema.
|
|
515
|
+
describe("unsupported kinds", () => {
|
|
516
|
+
it("counter throws clear error at construction", () => {
|
|
517
|
+
const CounterSchema = Schema.struct({
|
|
518
|
+
count: Schema.counter(),
|
|
529
519
|
})
|
|
530
520
|
|
|
531
|
-
expect(() =>
|
|
532
|
-
yjsSubstrateFactory.create(CounterSchema),
|
|
533
|
-
).toThrow("counter")
|
|
521
|
+
expect(() => yjsSubstrateFactory.create(CounterSchema)).toThrow("counter")
|
|
534
522
|
})
|
|
535
523
|
|
|
536
|
-
it("
|
|
537
|
-
const MovableSchema = Schema.
|
|
538
|
-
items: Schema.
|
|
524
|
+
it("movableList throws clear error at construction", () => {
|
|
525
|
+
const MovableSchema = Schema.struct({
|
|
526
|
+
items: Schema.movableList(Schema.string()),
|
|
539
527
|
})
|
|
540
528
|
|
|
541
|
-
expect(() =>
|
|
542
|
-
yjsSubstrateFactory.create(MovableSchema),
|
|
543
|
-
).toThrow("movable")
|
|
529
|
+
expect(() => yjsSubstrateFactory.create(MovableSchema)).toThrow("movable")
|
|
544
530
|
})
|
|
545
531
|
|
|
546
|
-
it("tree
|
|
547
|
-
const TreeSchema = Schema.
|
|
548
|
-
tree: Schema.
|
|
549
|
-
"tree",
|
|
550
|
-
Schema.struct({ label: Schema.string() }),
|
|
551
|
-
),
|
|
532
|
+
it("tree throws clear error at construction", () => {
|
|
533
|
+
const TreeSchema = Schema.struct({
|
|
534
|
+
tree: Schema.tree(Schema.struct({ label: Schema.string() })),
|
|
552
535
|
})
|
|
553
536
|
|
|
554
|
-
expect(() =>
|
|
555
|
-
yjsSubstrateFactory.create(TreeSchema),
|
|
556
|
-
).toThrow("tree")
|
|
537
|
+
expect(() => yjsSubstrateFactory.create(TreeSchema)).toThrow("tree")
|
|
557
538
|
})
|
|
558
539
|
})
|
|
559
540
|
|
|
560
541
|
// -------------------------------------------------------------------------
|
|
561
|
-
//
|
|
542
|
+
// fromEntirety
|
|
562
543
|
// -------------------------------------------------------------------------
|
|
563
544
|
|
|
564
|
-
describe("
|
|
545
|
+
describe("fromEntirety", () => {
|
|
565
546
|
it("rejects non-binary payloads", () => {
|
|
566
547
|
expect(() =>
|
|
567
|
-
yjsSubstrateFactory.
|
|
568
|
-
{ encoding: "json", data: "{}" },
|
|
548
|
+
yjsSubstrateFactory.fromEntirety(
|
|
549
|
+
{ kind: "entirety", encoding: "json", data: "{}" },
|
|
569
550
|
SimpleSchema,
|
|
570
551
|
),
|
|
571
552
|
).toThrow("binary")
|
|
@@ -579,8 +560,8 @@ describe("YjsSubstrate", () => {
|
|
|
579
560
|
d.items.push("x")
|
|
580
561
|
})
|
|
581
562
|
|
|
582
|
-
const payload =
|
|
583
|
-
const doc2 =
|
|
563
|
+
const payload = exportEntirety(doc)
|
|
564
|
+
const doc2 = createYjsDocFromEntirety(SimpleSchema, payload)
|
|
584
565
|
|
|
585
566
|
expect(doc2.title()).toBe("Snapshot Test")
|
|
586
567
|
expect(doc2.count()).toBe(77)
|
|
@@ -601,4 +582,4 @@ describe("YjsSubstrate", () => {
|
|
|
601
582
|
expect(parsed.compare(v)).toBe("equal")
|
|
602
583
|
})
|
|
603
584
|
})
|
|
604
|
-
})
|
|
585
|
+
})
|
|
@@ -36,7 +36,7 @@ describe("YjsVersion", () => {
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
it("round-trips a version vector with one peer", () => {
|
|
39
|
-
const v = versionAfterOps(
|
|
39
|
+
const v = versionAfterOps(doc => {
|
|
40
40
|
doc.getMap("root").set("title", "Hello")
|
|
41
41
|
})
|
|
42
42
|
const serialized = v.serialize()
|
|
@@ -66,7 +66,7 @@ describe("YjsVersion", () => {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
it("serialized form is a non-empty string", () => {
|
|
69
|
-
const v = versionAfterOps(
|
|
69
|
+
const v = versionAfterOps(doc => {
|
|
70
70
|
doc.getMap("root").set("count", 42)
|
|
71
71
|
})
|
|
72
72
|
const s = v.serialize()
|
|
@@ -89,7 +89,7 @@ describe("YjsVersion", () => {
|
|
|
89
89
|
|
|
90
90
|
describe("compare", () => {
|
|
91
91
|
it("returns 'equal' for the same version vector", () => {
|
|
92
|
-
const v = versionAfterOps(
|
|
92
|
+
const v = versionAfterOps(doc => {
|
|
93
93
|
doc.getMap("root").set("t", "hi")
|
|
94
94
|
})
|
|
95
95
|
expect(v.compare(v)).toBe("equal")
|
|
@@ -152,10 +152,7 @@ describe("YjsVersion", () => {
|
|
|
152
152
|
doc2.getMap("root").set("t", "B")
|
|
153
153
|
|
|
154
154
|
// Sync doc1 → doc2 only (doc2 knows about both, doc1 only knows itself)
|
|
155
|
-
const update = Y.encodeStateAsUpdate(
|
|
156
|
-
doc1,
|
|
157
|
-
Y.encodeStateVector(doc2),
|
|
158
|
-
)
|
|
155
|
+
const update = Y.encodeStateAsUpdate(doc1, Y.encodeStateVector(doc2))
|
|
159
156
|
Y.applyUpdate(doc2, update)
|
|
160
157
|
|
|
161
158
|
const v1 = new YjsVersion(Y.encodeStateVector(doc1))
|
|
@@ -175,14 +172,8 @@ describe("YjsVersion", () => {
|
|
|
175
172
|
doc2.getMap("root").set("t", "B")
|
|
176
173
|
|
|
177
174
|
// Bidirectional sync
|
|
178
|
-
const u1to2 = Y.encodeStateAsUpdate(
|
|
179
|
-
|
|
180
|
-
Y.encodeStateVector(doc2),
|
|
181
|
-
)
|
|
182
|
-
const u2to1 = Y.encodeStateAsUpdate(
|
|
183
|
-
doc2,
|
|
184
|
-
Y.encodeStateVector(doc1),
|
|
185
|
-
)
|
|
175
|
+
const u1to2 = Y.encodeStateAsUpdate(doc1, Y.encodeStateVector(doc2))
|
|
176
|
+
const u2to1 = Y.encodeStateAsUpdate(doc2, Y.encodeStateVector(doc1))
|
|
186
177
|
Y.applyUpdate(doc2, u1to2)
|
|
187
178
|
Y.applyUpdate(doc1, u2to1)
|
|
188
179
|
|
|
@@ -196,6 +187,7 @@ describe("YjsVersion", () => {
|
|
|
196
187
|
const fake = {
|
|
197
188
|
serialize: () => "fake",
|
|
198
189
|
compare: () => "equal" as const,
|
|
190
|
+
meet: () => fake,
|
|
199
191
|
}
|
|
200
192
|
expect(() => v.compare(fake)).toThrow(
|
|
201
193
|
"YjsVersion can only be compared with another YjsVersion",
|
|
@@ -224,4 +216,78 @@ describe("YjsVersion", () => {
|
|
|
224
216
|
expect(late.compare(earlyParsed)).toBe("ahead")
|
|
225
217
|
})
|
|
226
218
|
})
|
|
227
|
-
|
|
219
|
+
|
|
220
|
+
// -------------------------------------------------------------------------
|
|
221
|
+
// meet
|
|
222
|
+
// -------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
describe("YjsVersion.meet()", () => {
|
|
225
|
+
it("meet of concurrent versions produces component-wise minimum", () => {
|
|
226
|
+
// Create two docs with independent edits
|
|
227
|
+
const doc1 = new Y.Doc()
|
|
228
|
+
const doc2 = new Y.Doc()
|
|
229
|
+
|
|
230
|
+
doc1.getMap("root").set("a", 1)
|
|
231
|
+
doc1.getMap("root").set("b", 2)
|
|
232
|
+
doc2.getMap("root").set("c", 3)
|
|
233
|
+
|
|
234
|
+
const v1 = new YjsVersion(Y.encodeStateVector(doc1))
|
|
235
|
+
const v2 = new YjsVersion(Y.encodeStateVector(doc2))
|
|
236
|
+
|
|
237
|
+
// meet of concurrent versions — result ≤ both
|
|
238
|
+
const meet = v1.meet(v2) as YjsVersion
|
|
239
|
+
expect(meet.compare(v1)).not.toBe("ahead")
|
|
240
|
+
expect(meet.compare(v2)).not.toBe("ahead")
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it("meet of identical versions returns an equal version", () => {
|
|
244
|
+
const doc = new Y.Doc()
|
|
245
|
+
doc.getMap("root").set("x", 1)
|
|
246
|
+
const v = new YjsVersion(Y.encodeStateVector(doc))
|
|
247
|
+
|
|
248
|
+
const meet = v.meet(v) as YjsVersion
|
|
249
|
+
expect(meet.compare(v)).toBe("equal")
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it("meet round-trips through Yjs decode correctly", () => {
|
|
253
|
+
// The custom encodeStateVector must produce bytes that Yjs can decode
|
|
254
|
+
const doc1 = new Y.Doc()
|
|
255
|
+
const doc2 = new Y.Doc()
|
|
256
|
+
|
|
257
|
+
doc1.getMap("root").set("x", 1)
|
|
258
|
+
doc1.getMap("root").set("y", 2)
|
|
259
|
+
|
|
260
|
+
// Sync doc1 → doc2, then doc2 makes independent edits
|
|
261
|
+
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1))
|
|
262
|
+
doc2.getMap("root").set("z", 3)
|
|
263
|
+
|
|
264
|
+
const v1 = new YjsVersion(Y.encodeStateVector(doc1))
|
|
265
|
+
const v2 = new YjsVersion(Y.encodeStateVector(doc2))
|
|
266
|
+
|
|
267
|
+
// v1 is behind v2 (v2 has all of v1's ops plus its own)
|
|
268
|
+
expect(v1.compare(v2)).toBe("behind")
|
|
269
|
+
|
|
270
|
+
// meet(v1, v2) should equal v1 (the behind one)
|
|
271
|
+
const meet = v1.meet(v2) as YjsVersion
|
|
272
|
+
expect(meet.compare(v1)).toBe("equal")
|
|
273
|
+
|
|
274
|
+
// The meet's state vector bytes can be decoded by Yjs
|
|
275
|
+
const decoded = Y.decodeStateVector(meet.sv)
|
|
276
|
+
expect(decoded.size).toBeGreaterThan(0)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it("meet of two behind-ahead versions gives the behind one", () => {
|
|
280
|
+
const doc = new Y.Doc()
|
|
281
|
+
doc.getMap("root").set("a", 1)
|
|
282
|
+
const early = new YjsVersion(Y.encodeStateVector(doc))
|
|
283
|
+
|
|
284
|
+
doc.getMap("root").set("b", 2)
|
|
285
|
+
const late = new YjsVersion(Y.encodeStateVector(doc))
|
|
286
|
+
|
|
287
|
+
expect(early.compare(late)).toBe("behind")
|
|
288
|
+
|
|
289
|
+
const meet = early.meet(late) as YjsVersion
|
|
290
|
+
expect(meet.compare(early)).toBe("equal")
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
})
|