@vuer-ai/vuer-rtc 0.4.2 → 0.5.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/CLAUDE.md +29 -0
- package/COALESCE_FIX_VERIFICATION.md +81 -0
- package/REFACTORING_NOTES.md +229 -0
- package/dist/client/EditBuffer.d.ts +13 -1
- package/dist/client/EditBuffer.d.ts.map +1 -1
- package/dist/client/EditBuffer.js +47 -3
- package/dist/client/EditBuffer.js.map +1 -1
- package/dist/client/actions.d.ts +5 -1
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +12 -9
- package/dist/client/actions.js.map +1 -1
- package/dist/client/coalesceGraphOps.d.ts +34 -0
- package/dist/client/coalesceGraphOps.d.ts.map +1 -0
- package/dist/client/coalesceGraphOps.js +35 -0
- package/dist/client/coalesceGraphOps.js.map +1 -0
- package/dist/client/coalesceTextOperations.d.ts +42 -0
- package/dist/client/coalesceTextOperations.d.ts.map +1 -0
- package/dist/client/coalesceTextOperations.js +119 -0
- package/dist/client/coalesceTextOperations.js.map +1 -0
- package/dist/client/coalescence/index.d.ts +9 -0
- package/dist/client/coalescence/index.d.ts.map +1 -0
- package/dist/client/coalescence/index.js +9 -0
- package/dist/client/coalescence/index.js.map +1 -0
- package/dist/client/coalescence/registry.d.ts +48 -0
- package/dist/client/coalescence/registry.d.ts.map +1 -0
- package/dist/client/coalescence/registry.js +95 -0
- package/dist/client/coalescence/registry.js.map +1 -0
- package/dist/client/coalescence/textDeletes.d.ts +38 -0
- package/dist/client/coalescence/textDeletes.d.ts.map +1 -0
- package/dist/client/coalescence/textDeletes.js +68 -0
- package/dist/client/coalescence/textDeletes.js.map +1 -0
- package/dist/client/coalescence/textInserts.d.ts +45 -0
- package/dist/client/coalescence/textInserts.d.ts.map +1 -0
- package/dist/client/coalescence/textInserts.js +96 -0
- package/dist/client/coalescence/textInserts.js.map +1 -0
- package/dist/client/createGraph.d.ts.map +1 -1
- package/dist/client/createGraph.js +9 -2
- package/dist/client/createGraph.js.map +1 -1
- package/dist/client/createTextDocument.d.ts.map +1 -1
- package/dist/client/createTextDocument.js +2 -1
- package/dist/client/createTextDocument.js.map +1 -1
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/textActions.d.ts +1 -1
- package/dist/client/textActions.d.ts.map +1 -1
- package/dist/client/textActions.js +7 -2
- package/dist/client/textActions.js.map +1 -1
- package/dist/client/types.d.ts +3 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.d.ts +0 -4
- package/dist/crdt/GraphTextCRDT.d.ts.map +1 -1
- package/dist/crdt/GraphTextCRDT.js +3 -0
- package/dist/crdt/GraphTextCRDT.js.map +1 -1
- package/dist/crdt/Rope.d.ts +27 -6
- package/dist/crdt/Rope.d.ts.map +1 -1
- package/dist/crdt/Rope.js +137 -69
- package/dist/crdt/Rope.js.map +1 -1
- package/dist/operations/OperationTypes.d.ts +10 -26
- package/dist/operations/OperationTypes.d.ts.map +1 -1
- package/dist/operations/apply/text.d.ts.map +1 -1
- package/dist/operations/apply/text.js +8 -16
- package/dist/operations/apply/text.js.map +1 -1
- package/examples/05-coalescence-usage.ts +189 -0
- package/package.json +1 -1
- package/src/client/EditBuffer.ts +51 -3
- package/src/client/actions.ts +13 -9
- package/src/client/coalesceGraphOps.ts +40 -0
- package/src/client/coalesceTextOperations.ts +134 -0
- package/src/client/coalescence/index.ts +18 -0
- package/src/client/coalescence/registry.ts +137 -0
- package/src/client/coalescence/textDeletes.ts +94 -0
- package/src/client/coalescence/textInserts.ts +128 -0
- package/src/client/createGraph.ts +11 -2
- package/src/client/createTextDocument.ts +2 -1
- package/src/client/index.ts +14 -0
- package/src/client/textActions.ts +9 -2
- package/src/client/types.ts +4 -1
- package/src/crdt/GraphTextCRDT.ts +0 -5
- package/src/crdt/Rope.ts +155 -79
- package/src/operations/OperationTypes.ts +10 -8
- package/src/operations/apply/text.ts +8 -20
- package/test-coalescence.ts +201 -0
- package/tests/client/actions.test.ts +156 -0
- package/tests/client/coalesce-text-operations.test.ts +327 -0
- package/tests/client/edit-buffer.test.ts +137 -1
- package/tests/crdt/graph-text-crdt.test.ts +29 -17
- package/tests/crdt/rope.test.ts +13 -11
|
@@ -6,7 +6,7 @@ import { describe, it, expect } from '@jest/globals';
|
|
|
6
6
|
import { readFileSync } from 'fs';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
-
import { isAdditiveOp, mergeValues, EditBufferImpl } from '../../src/client/EditBuffer.js';
|
|
9
|
+
import { isAdditiveOp, mergeValues, EditBufferImpl, coalesceTextOps } from '../../src/client/EditBuffer.js';
|
|
10
10
|
import type { Operation } from '../../src/operations/OperationTypes.js';
|
|
11
11
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -114,4 +114,140 @@ describe('EditBuffer', () => {
|
|
|
114
114
|
expect(buffer.getOps()).toHaveLength(0);
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
|
+
|
|
118
|
+
describe('coalesceTextOps', () => {
|
|
119
|
+
it('should NOT coalesce text inserts with CRDT metadata', () => {
|
|
120
|
+
const ops: Operation[] = [
|
|
121
|
+
{
|
|
122
|
+
otype: 'text.insert',
|
|
123
|
+
key: 'text-doc',
|
|
124
|
+
path: 'content',
|
|
125
|
+
position: 0,
|
|
126
|
+
value: 'a',
|
|
127
|
+
id: { agent: 'session-1', seq: 1 },
|
|
128
|
+
content: 'a',
|
|
129
|
+
parentId: null,
|
|
130
|
+
seq: 1,
|
|
131
|
+
} as any,
|
|
132
|
+
{
|
|
133
|
+
otype: 'text.insert',
|
|
134
|
+
key: 'text-doc',
|
|
135
|
+
path: 'content',
|
|
136
|
+
position: 1,
|
|
137
|
+
value: 'b',
|
|
138
|
+
id: { agent: 'session-1', seq: 2 },
|
|
139
|
+
content: 'b',
|
|
140
|
+
parentId: { agent: 'session-1', seq: 1 },
|
|
141
|
+
seq: 2,
|
|
142
|
+
} as any,
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const result = coalesceTextOps(ops);
|
|
146
|
+
|
|
147
|
+
// Should NOT coalesce - each operation keeps its CRDT metadata
|
|
148
|
+
expect(result).toHaveLength(2);
|
|
149
|
+
expect((result[0] as any).id).toEqual({ agent: 'session-1', seq: 1 });
|
|
150
|
+
expect((result[0] as any).value).toBe('a');
|
|
151
|
+
expect((result[1] as any).id).toEqual({ agent: 'session-1', seq: 2 });
|
|
152
|
+
expect((result[1] as any).value).toBe('b');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should coalesce position-based text inserts without CRDT metadata', () => {
|
|
156
|
+
const ops: Operation[] = [
|
|
157
|
+
{
|
|
158
|
+
otype: 'text.insert',
|
|
159
|
+
key: 'text-doc',
|
|
160
|
+
path: 'content',
|
|
161
|
+
position: 0,
|
|
162
|
+
value: 'a',
|
|
163
|
+
} as any,
|
|
164
|
+
{
|
|
165
|
+
otype: 'text.insert',
|
|
166
|
+
key: 'text-doc',
|
|
167
|
+
path: 'content',
|
|
168
|
+
position: 1,
|
|
169
|
+
value: 'b',
|
|
170
|
+
} as any,
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const result = coalesceTextOps(ops);
|
|
174
|
+
|
|
175
|
+
// Should coalesce position-based operations
|
|
176
|
+
expect(result).toHaveLength(1);
|
|
177
|
+
expect((result[0] as any).position).toBe(0);
|
|
178
|
+
expect((result[0] as any).value).toBe('ab');
|
|
179
|
+
expect((result[0] as any).id).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should NOT coalesce text deletes with CRDT metadata', () => {
|
|
183
|
+
const ops: Operation[] = [
|
|
184
|
+
{
|
|
185
|
+
otype: 'text.delete',
|
|
186
|
+
key: 'text-doc',
|
|
187
|
+
path: 'content',
|
|
188
|
+
position: 0,
|
|
189
|
+
length: 1,
|
|
190
|
+
deletions: [{ id: { agent: 'session-1', seq: 1 }, length: 1 }],
|
|
191
|
+
} as any,
|
|
192
|
+
{
|
|
193
|
+
otype: 'text.delete',
|
|
194
|
+
key: 'text-doc',
|
|
195
|
+
path: 'content',
|
|
196
|
+
position: 0,
|
|
197
|
+
length: 1,
|
|
198
|
+
deletions: [{ id: { agent: 'session-1', seq: 2 }, length: 1 }],
|
|
199
|
+
} as any,
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const result = coalesceTextOps(ops);
|
|
203
|
+
|
|
204
|
+
// Should NOT coalesce - each operation keeps its CRDT metadata
|
|
205
|
+
expect(result).toHaveLength(2);
|
|
206
|
+
expect((result[0] as any).deletions).toEqual([{ id: { agent: 'session-1', seq: 1 }, length: 1 }]);
|
|
207
|
+
expect((result[1] as any).deletions).toEqual([{ id: { agent: 'session-1', seq: 2 }, length: 1 }]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should preserve CRDT metadata through mixed operations', () => {
|
|
211
|
+
const ops: Operation[] = [
|
|
212
|
+
{
|
|
213
|
+
otype: 'text.insert',
|
|
214
|
+
key: 'text-doc',
|
|
215
|
+
path: 'content',
|
|
216
|
+
position: 0,
|
|
217
|
+
value: 'hello',
|
|
218
|
+
} as any,
|
|
219
|
+
{
|
|
220
|
+
otype: 'text.insert',
|
|
221
|
+
key: 'text-doc',
|
|
222
|
+
path: 'content',
|
|
223
|
+
position: 5,
|
|
224
|
+
value: ' ',
|
|
225
|
+
id: { agent: 'session-1', seq: 1 },
|
|
226
|
+
content: ' ',
|
|
227
|
+
parentId: null,
|
|
228
|
+
seq: 1,
|
|
229
|
+
} as any,
|
|
230
|
+
{
|
|
231
|
+
otype: 'text.insert',
|
|
232
|
+
key: 'text-doc',
|
|
233
|
+
path: 'content',
|
|
234
|
+
position: 6,
|
|
235
|
+
value: 'world',
|
|
236
|
+
} as any,
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
const result = coalesceTextOps(ops);
|
|
240
|
+
|
|
241
|
+
// First "hello" should be alone (position-based, coalesced)
|
|
242
|
+
// Second " " should be alone (has CRDT metadata)
|
|
243
|
+
// Third "world" should be alone (position-based but not adjacent to first)
|
|
244
|
+
expect(result).toHaveLength(3);
|
|
245
|
+
expect((result[0] as any).value).toBe('hello');
|
|
246
|
+
expect((result[0] as any).id).toBeUndefined();
|
|
247
|
+
expect((result[1] as any).value).toBe(' ');
|
|
248
|
+
expect((result[1] as any).id).toEqual({ agent: 'session-1', seq: 1 });
|
|
249
|
+
expect((result[2] as any).value).toBe('world');
|
|
250
|
+
expect((result[2] as any).id).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
117
253
|
});
|
|
@@ -96,10 +96,11 @@ describe('GraphTextCRDT', () => {
|
|
|
96
96
|
it('should return InsertOp with CRDT metadata', () => {
|
|
97
97
|
const op = crdt.insertLocal('node-1', 'content', 0, 'test');
|
|
98
98
|
expect(op.id).toBeDefined();
|
|
99
|
-
expect(op.id
|
|
100
|
-
expect(op.id
|
|
99
|
+
expect(typeof op.id).toBe('string');
|
|
100
|
+
expect(op.id).toMatch(/^session-1:/); // String format: "agent:seq"
|
|
101
101
|
expect(op.content).toBe('test');
|
|
102
102
|
expect(op.seq).toBeDefined();
|
|
103
|
+
expect(op.ts).toBeDefined();
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
it('should handle multiple sequential inserts', () => {
|
|
@@ -112,7 +113,7 @@ describe('GraphTextCRDT', () => {
|
|
|
112
113
|
it('should auto-create rope if not initialized', () => {
|
|
113
114
|
const op = crdt.insertLocal('node-2', 'title', 0, 'New Title');
|
|
114
115
|
expect(crdt.getText('node-2', 'title')).toBe('New Title');
|
|
115
|
-
expect(op.id
|
|
116
|
+
expect(op.id).toMatch(/^session-1:/);
|
|
116
117
|
});
|
|
117
118
|
});
|
|
118
119
|
|
|
@@ -216,10 +217,11 @@ describe('GraphTextCRDT', () => {
|
|
|
216
217
|
|
|
217
218
|
it('should apply remote insert operation', () => {
|
|
218
219
|
const op: InsertOp = {
|
|
219
|
-
id:
|
|
220
|
+
id: 'session-2:0',
|
|
220
221
|
content: 'Hello',
|
|
221
222
|
parentId: null,
|
|
222
223
|
seq: 1,
|
|
224
|
+
ts: Date.now() / 1000,
|
|
223
225
|
};
|
|
224
226
|
crdt.applyInsert('node-1', 'content', op);
|
|
225
227
|
expect(crdt.getText('node-1', 'content')).toBe('Hello');
|
|
@@ -227,18 +229,20 @@ describe('GraphTextCRDT', () => {
|
|
|
227
229
|
|
|
228
230
|
it('should apply remote insert with parent', () => {
|
|
229
231
|
const op1: InsertOp = {
|
|
230
|
-
id:
|
|
232
|
+
id: 'session-1:0',
|
|
231
233
|
content: 'Hello',
|
|
232
234
|
parentId: null,
|
|
233
235
|
seq: 1,
|
|
236
|
+
ts: Date.now() / 1000,
|
|
234
237
|
};
|
|
235
238
|
crdt.applyInsert('node-1', 'content', op1);
|
|
236
239
|
|
|
237
240
|
const op2: InsertOp = {
|
|
238
|
-
id:
|
|
241
|
+
id: 'session-2:0',
|
|
239
242
|
content: ' World',
|
|
240
|
-
parentId:
|
|
243
|
+
parentId: 'session-1:4',
|
|
241
244
|
seq: 2,
|
|
245
|
+
ts: Date.now() / 1000,
|
|
242
246
|
};
|
|
243
247
|
crdt.applyInsert('node-1', 'content', op2);
|
|
244
248
|
expect(crdt.getText('node-1', 'content')).toBe('Hello World');
|
|
@@ -246,10 +250,11 @@ describe('GraphTextCRDT', () => {
|
|
|
246
250
|
|
|
247
251
|
it('should auto-create rope if not initialized', () => {
|
|
248
252
|
const op: InsertOp = {
|
|
249
|
-
id:
|
|
253
|
+
id: 'session-2:0',
|
|
250
254
|
content: 'test',
|
|
251
255
|
parentId: null,
|
|
252
256
|
seq: 1,
|
|
257
|
+
ts: Date.now() / 1000,
|
|
253
258
|
};
|
|
254
259
|
crdt.applyInsert('node-2', 'title', op);
|
|
255
260
|
expect(crdt.getText('node-2', 'title')).toBe('test');
|
|
@@ -259,10 +264,11 @@ describe('GraphTextCRDT', () => {
|
|
|
259
264
|
const op1 = crdt.insertLocal('node-1', 'content', 0, 'A');
|
|
260
265
|
|
|
261
266
|
const op2: InsertOp = {
|
|
262
|
-
id:
|
|
267
|
+
id: 'session-2:0',
|
|
263
268
|
content: 'B',
|
|
264
269
|
parentId: null,
|
|
265
270
|
seq: 1,
|
|
271
|
+
ts: Date.now() / 1000,
|
|
266
272
|
};
|
|
267
273
|
crdt.applyInsert('node-1', 'content', op2);
|
|
268
274
|
|
|
@@ -301,7 +307,7 @@ describe('GraphTextCRDT', () => {
|
|
|
301
307
|
|
|
302
308
|
it('should auto-create rope if not initialized', () => {
|
|
303
309
|
const op: DeleteOp = {
|
|
304
|
-
deletions: [{ id:
|
|
310
|
+
deletions: [{ id: 'session-1:0', length: 5 }],
|
|
305
311
|
};
|
|
306
312
|
crdt.applyDelete('node-2', 'title', op);
|
|
307
313
|
expect(crdt.getText('node-2', 'title')).toBe('');
|
|
@@ -311,7 +317,7 @@ describe('GraphTextCRDT', () => {
|
|
|
311
317
|
const delOp = crdt.deleteLocal('node-1', 'content', 0, 5);
|
|
312
318
|
|
|
313
319
|
const remoteOp: DeleteOp = {
|
|
314
|
-
deletions: [{ id:
|
|
320
|
+
deletions: [{ id: 'session-1:5', length: 6 }],
|
|
315
321
|
};
|
|
316
322
|
crdt.applyDelete('node-1', 'content', remoteOp);
|
|
317
323
|
|
|
@@ -336,10 +342,11 @@ describe('GraphTextCRDT', () => {
|
|
|
336
342
|
deletions: [{ id: items[0].id, length: 5 }],
|
|
337
343
|
},
|
|
338
344
|
insert: {
|
|
339
|
-
id:
|
|
345
|
+
id: 'session-2:0',
|
|
340
346
|
content: 'Hi',
|
|
341
347
|
parentId: null,
|
|
342
348
|
seq: 2,
|
|
349
|
+
ts: Date.now() / 1000,
|
|
343
350
|
},
|
|
344
351
|
};
|
|
345
352
|
crdt.applyReplace('node-1', 'content', op);
|
|
@@ -355,10 +362,11 @@ describe('GraphTextCRDT', () => {
|
|
|
355
362
|
deletions: [{ id: items[0].id, length: 6 }],
|
|
356
363
|
},
|
|
357
364
|
insert: {
|
|
358
|
-
id:
|
|
365
|
+
id: 'session-2:0',
|
|
359
366
|
content: '',
|
|
360
367
|
parentId: null,
|
|
361
368
|
seq: 2,
|
|
369
|
+
ts: Date.now() / 1000,
|
|
362
370
|
},
|
|
363
371
|
};
|
|
364
372
|
crdt.applyReplace('node-1', 'content', op);
|
|
@@ -369,10 +377,11 @@ describe('GraphTextCRDT', () => {
|
|
|
369
377
|
const op: ReplaceOp = {
|
|
370
378
|
delete: { deletions: [] },
|
|
371
379
|
insert: {
|
|
372
|
-
id:
|
|
380
|
+
id: 'session-2:0',
|
|
373
381
|
content: 'new',
|
|
374
382
|
parentId: null,
|
|
375
383
|
seq: 1,
|
|
384
|
+
ts: Date.now() / 1000,
|
|
376
385
|
},
|
|
377
386
|
};
|
|
378
387
|
crdt.applyReplace('node-2', 'title', op);
|
|
@@ -562,10 +571,11 @@ describe('GraphTextCRDT', () => {
|
|
|
562
571
|
|
|
563
572
|
// Initialize both from the same initial state
|
|
564
573
|
const initOp: InsertOp = {
|
|
565
|
-
id:
|
|
574
|
+
id: 'init:0',
|
|
566
575
|
content: 'Hello',
|
|
567
576
|
parentId: null,
|
|
568
577
|
seq: 1,
|
|
578
|
+
ts: Date.now() / 1000,
|
|
569
579
|
};
|
|
570
580
|
crdt1.applyInsert('doc-1', 'content', initOp);
|
|
571
581
|
crdt2.applyInsert('doc-1', 'content', initOp);
|
|
@@ -590,10 +600,11 @@ describe('GraphTextCRDT', () => {
|
|
|
590
600
|
|
|
591
601
|
// Initialize from same initial state
|
|
592
602
|
const initOp: InsertOp = {
|
|
593
|
-
id:
|
|
603
|
+
id: 'init:0',
|
|
594
604
|
content: 'ABCDEF',
|
|
595
605
|
parentId: null,
|
|
596
606
|
seq: 1,
|
|
607
|
+
ts: Date.now() / 1000,
|
|
597
608
|
};
|
|
598
609
|
crdt1.applyInsert('doc-1', 'content', initOp);
|
|
599
610
|
crdt2.applyInsert('doc-1', 'content', initOp);
|
|
@@ -615,10 +626,11 @@ describe('GraphTextCRDT', () => {
|
|
|
615
626
|
|
|
616
627
|
// Initialize from same initial state
|
|
617
628
|
const initOp: InsertOp = {
|
|
618
|
-
id:
|
|
629
|
+
id: 'init:0',
|
|
619
630
|
content: 'test',
|
|
620
631
|
parentId: null,
|
|
621
632
|
seq: 1,
|
|
633
|
+
ts: Date.now() / 1000,
|
|
622
634
|
};
|
|
623
635
|
crdt1.applyInsert('doc-1', 'content', initOp);
|
|
624
636
|
crdt2.applyInsert('doc-1', 'content', initOp);
|
package/tests/crdt/rope.test.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
getStats,
|
|
25
25
|
itemIdEquals,
|
|
26
26
|
itemIdCompare,
|
|
27
|
+
parseItemId,
|
|
27
28
|
toRaw,
|
|
28
29
|
fromRaw,
|
|
29
30
|
hydrateRope,
|
|
@@ -276,28 +277,28 @@ describe('TextRope', () => {
|
|
|
276
277
|
|
|
277
278
|
describe('ItemId Utilities', () => {
|
|
278
279
|
it('should compare equal ItemIds', () => {
|
|
279
|
-
const id1: ItemId =
|
|
280
|
-
const id2: ItemId =
|
|
280
|
+
const id1: ItemId = 'a:1';
|
|
281
|
+
const id2: ItemId = 'a:1';
|
|
281
282
|
expect(itemIdEquals(id1, id2)).toBe(true);
|
|
282
283
|
});
|
|
283
284
|
|
|
284
285
|
it('should compare different ItemIds', () => {
|
|
285
|
-
const id1: ItemId =
|
|
286
|
-
const id2: ItemId =
|
|
286
|
+
const id1: ItemId = 'a:1';
|
|
287
|
+
const id2: ItemId = 'a:2';
|
|
287
288
|
expect(itemIdEquals(id1, id2)).toBe(false);
|
|
288
289
|
});
|
|
289
290
|
|
|
290
291
|
it('should handle null ItemIds', () => {
|
|
291
|
-
const id1: ItemId =
|
|
292
|
+
const id1: ItemId = 'a:1';
|
|
292
293
|
expect(itemIdEquals(null, null)).toBe(true);
|
|
293
294
|
expect(itemIdEquals(id1, null)).toBe(false);
|
|
294
295
|
expect(itemIdEquals(null, id1)).toBe(false);
|
|
295
296
|
});
|
|
296
297
|
|
|
297
298
|
it('should compare ItemIds for ordering', () => {
|
|
298
|
-
const id1: ItemId =
|
|
299
|
-
const id2: ItemId =
|
|
300
|
-
const id3: ItemId =
|
|
299
|
+
const id1: ItemId = 'a:1';
|
|
300
|
+
const id2: ItemId = 'b:1';
|
|
301
|
+
const id3: ItemId = 'a:2';
|
|
301
302
|
|
|
302
303
|
expect(itemIdCompare(id1, id2)).toBeLessThan(0);
|
|
303
304
|
expect(itemIdCompare(id2, id1)).toBeGreaterThan(0);
|
|
@@ -384,7 +385,7 @@ describe('TextRope', () => {
|
|
|
384
385
|
|
|
385
386
|
it('should return -1 for non-existent ID', () => {
|
|
386
387
|
const rope = create('agent-1');
|
|
387
|
-
const index = findItemById(rope,
|
|
388
|
+
const index = findItemById(rope, 'unknown:999');
|
|
388
389
|
expect(index).toBe(-1);
|
|
389
390
|
});
|
|
390
391
|
});
|
|
@@ -1090,7 +1091,7 @@ describe('TextRope', () => {
|
|
|
1090
1091
|
apply(rope1, ins2);
|
|
1091
1092
|
apply(rope2, ins1);
|
|
1092
1093
|
// Also need to sync the delete
|
|
1093
|
-
const delOp = { deletions: [{ id:
|
|
1094
|
+
const delOp = { deletions: [{ id: 'agent-1:2', length: 2 }] };
|
|
1094
1095
|
applyDelete(rope2, delOp);
|
|
1095
1096
|
|
|
1096
1097
|
expect(getText(rope1)).toBe(getText(rope2));
|
|
@@ -1305,7 +1306,8 @@ describe('TextRope', () => {
|
|
|
1305
1306
|
|
|
1306
1307
|
// New inserts should have seq > maxSeq
|
|
1307
1308
|
const newOp = insert(restored, 4, ' more');
|
|
1308
|
-
|
|
1309
|
+
const parsed = parseItemId(newOp.id);
|
|
1310
|
+
expect(parsed.seq).toBeGreaterThan(expectedMaxSeq);
|
|
1309
1311
|
});
|
|
1310
1312
|
});
|
|
1311
1313
|
});
|