@verdant-web/common 2.7.2 → 2.8.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/esm/diffing.d.ts +39 -0
- package/dist/esm/diffing.js +257 -0
- package/dist/esm/diffing.js.map +1 -0
- package/dist/esm/diffing.test.d.ts +1 -0
- package/dist/esm/diffing.test.js +896 -0
- package/dist/esm/diffing.test.js.map +1 -0
- package/dist/esm/index.d.ts +20 -19
- package/dist/esm/index.js +17 -16
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/migration.js +1 -1
- package/dist/esm/migration.js.map +1 -1
- package/dist/esm/migration.test.d.ts +1 -0
- package/dist/esm/migration.test.js +98 -0
- package/dist/esm/migration.test.js.map +1 -0
- package/dist/esm/operation.d.ts +7 -21
- package/dist/esm/operation.js +28 -266
- package/dist/esm/operation.js.map +1 -1
- package/dist/esm/operation.test.js +122 -592
- package/dist/esm/operation.test.js.map +1 -1
- package/dist/esm/patch.d.ts +13 -13
- package/dist/esm/patch.js +41 -16
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/timestamp.d.ts +1 -0
- package/dist/esm/timestamp.js +3 -0
- package/dist/esm/timestamp.js.map +1 -1
- package/dist/esm/undo.js +15 -2
- package/dist/esm/undo.js.map +1 -1
- package/dist/esm/undo.test.d.ts +1 -0
- package/dist/esm/undo.test.js +20 -0
- package/dist/esm/undo.test.js.map +1 -0
- package/dist/esm/utils.js +13 -3
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/diffing.test.ts +939 -0
- package/src/diffing.ts +325 -0
- package/src/index.ts +21 -20
- package/src/migration.test.ts +112 -0
- package/src/migration.ts +2 -1
- package/src/operation.test.ts +148 -623
- package/src/operation.ts +37 -371
- package/src/patch.ts +68 -11
- package/src/timestamp.ts +4 -1
- package/src/undo.test.ts +21 -0
- package/src/undo.ts +15 -2
- package/src/utils.ts +13 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createFileRef } from './files.js';
|
|
3
2
|
import { assignOid, assignOidsToAllSubObjects, createRef, getOid, } from './oids.js';
|
|
4
|
-
import {
|
|
3
|
+
import { applyOperations, applyPatch, deconstructSnapshotToRefs, initialToPatches, substituteRefsWithObjects, } from './operation.js';
|
|
5
4
|
function createClock(init = 0) {
|
|
6
5
|
let i = init;
|
|
7
6
|
const clock = () => (i++).toString();
|
|
@@ -12,206 +11,7 @@ function createClock(init = 0) {
|
|
|
12
11
|
});
|
|
13
12
|
return clock;
|
|
14
13
|
}
|
|
15
|
-
describe('
|
|
16
|
-
describe('on flat objects', () => {
|
|
17
|
-
it('generates and applies set and remove operations', () => {
|
|
18
|
-
const from = { foo: 'bar', baz: 'qux', zing: 1 };
|
|
19
|
-
assignOid(from, 'test/a');
|
|
20
|
-
const to = { foo: 'bar', baz: 'pop' };
|
|
21
|
-
assignOid(to, 'test/a');
|
|
22
|
-
const ops = diffToPatches(from, to, createClock());
|
|
23
|
-
expect(ops).toMatchInlineSnapshot(`
|
|
24
|
-
[
|
|
25
|
-
{
|
|
26
|
-
"data": {
|
|
27
|
-
"name": "baz",
|
|
28
|
-
"op": "set",
|
|
29
|
-
"value": "pop",
|
|
30
|
-
},
|
|
31
|
-
"oid": "test/a",
|
|
32
|
-
"timestamp": "0",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"data": {
|
|
36
|
-
"name": "zing",
|
|
37
|
-
"op": "remove",
|
|
38
|
-
},
|
|
39
|
-
"oid": "test/a",
|
|
40
|
-
"timestamp": "1",
|
|
41
|
-
},
|
|
42
|
-
]
|
|
43
|
-
`);
|
|
44
|
-
const result = applyOperations(from, ops);
|
|
45
|
-
expect(result).toEqual(to);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe('on nested objects', () => {
|
|
49
|
-
it('replaces whole nested objects', () => {
|
|
50
|
-
const from = assignOid({
|
|
51
|
-
foo: 'bar',
|
|
52
|
-
baz: assignOid({ qux: 'corge' }, 'test/a:0'),
|
|
53
|
-
}, 'test/a');
|
|
54
|
-
const to = assignOid({
|
|
55
|
-
foo: 'bar',
|
|
56
|
-
baz: assignOid({ qux: 'grault' }, 'test/a:1'),
|
|
57
|
-
}, 'test/a');
|
|
58
|
-
const patches = diffToPatches(from, to, createClock());
|
|
59
|
-
expect(patches).toMatchInlineSnapshot(`
|
|
60
|
-
[
|
|
61
|
-
{
|
|
62
|
-
"data": {
|
|
63
|
-
"op": "initialize",
|
|
64
|
-
"value": {
|
|
65
|
-
"qux": "grault",
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
"oid": "test/a:1",
|
|
69
|
-
"timestamp": "0",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"data": {
|
|
73
|
-
"name": "baz",
|
|
74
|
-
"op": "set",
|
|
75
|
-
"value": {
|
|
76
|
-
"@@type": "ref",
|
|
77
|
-
"id": "test/a:1",
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
"oid": "test/a",
|
|
81
|
-
"timestamp": "1",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"data": {
|
|
85
|
-
"op": "delete",
|
|
86
|
-
},
|
|
87
|
-
"oid": "test/a:0",
|
|
88
|
-
"timestamp": "2",
|
|
89
|
-
},
|
|
90
|
-
]
|
|
91
|
-
`);
|
|
92
|
-
});
|
|
93
|
-
it('replaces whole nested objects with different ids even if fields are the same', () => {
|
|
94
|
-
const from = {
|
|
95
|
-
foo: 'bar',
|
|
96
|
-
baz: { qux: 'corge' },
|
|
97
|
-
};
|
|
98
|
-
assignOid(from, 'test/a');
|
|
99
|
-
assignOid(from.baz, 'test/a:0');
|
|
100
|
-
const to = {
|
|
101
|
-
foo: 'bar',
|
|
102
|
-
baz: { qux: 'corge' },
|
|
103
|
-
};
|
|
104
|
-
assignOid(to, 'test/a');
|
|
105
|
-
assignOid(to.baz, 'test/a:1');
|
|
106
|
-
const patches = diffToPatches(from, to, createClock());
|
|
107
|
-
expect(patches).toMatchInlineSnapshot(`
|
|
108
|
-
[
|
|
109
|
-
{
|
|
110
|
-
"data": {
|
|
111
|
-
"op": "initialize",
|
|
112
|
-
"value": {
|
|
113
|
-
"qux": "corge",
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
"oid": "test/a:1",
|
|
117
|
-
"timestamp": "0",
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"data": {
|
|
121
|
-
"name": "baz",
|
|
122
|
-
"op": "set",
|
|
123
|
-
"value": {
|
|
124
|
-
"@@type": "ref",
|
|
125
|
-
"id": "test/a:1",
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
"oid": "test/a",
|
|
129
|
-
"timestamp": "1",
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
"data": {
|
|
133
|
-
"op": "delete",
|
|
134
|
-
},
|
|
135
|
-
"oid": "test/a:0",
|
|
136
|
-
"timestamp": "2",
|
|
137
|
-
},
|
|
138
|
-
]
|
|
139
|
-
`);
|
|
140
|
-
});
|
|
141
|
-
it('does not replace objects with the same identity and same fields', () => {
|
|
142
|
-
const from = {
|
|
143
|
-
foo: 'bar',
|
|
144
|
-
baz: { qux: 'corge' },
|
|
145
|
-
};
|
|
146
|
-
assignOid(from, 'test/a');
|
|
147
|
-
assignOid(from.baz, 'test/a:0');
|
|
148
|
-
const to = {
|
|
149
|
-
foo: 'bar',
|
|
150
|
-
baz: { qux: 'corge' },
|
|
151
|
-
};
|
|
152
|
-
assignOid(to, 'test/a');
|
|
153
|
-
assignOid(to.baz, 'test/a:0');
|
|
154
|
-
const patches = diffToPatches(from, to, createClock());
|
|
155
|
-
expect(patches).toEqual([]);
|
|
156
|
-
});
|
|
157
|
-
it('patches sub-objects with same identity', () => {
|
|
158
|
-
const from = {
|
|
159
|
-
foo: 'bar',
|
|
160
|
-
baz: { qux: 'corge' },
|
|
161
|
-
};
|
|
162
|
-
assignOid(from, 'test/a');
|
|
163
|
-
assignOid(from.baz, 'test/a:0');
|
|
164
|
-
const to = {
|
|
165
|
-
foo: 'bar',
|
|
166
|
-
baz: { qux: 'fig' },
|
|
167
|
-
};
|
|
168
|
-
assignOid(to, 'test/a');
|
|
169
|
-
assignOid(to.baz, 'test/a:0');
|
|
170
|
-
const patches = diffToPatches(from, to, createClock());
|
|
171
|
-
expect(patches).toMatchInlineSnapshot(`
|
|
172
|
-
[
|
|
173
|
-
{
|
|
174
|
-
"data": {
|
|
175
|
-
"name": "qux",
|
|
176
|
-
"op": "set",
|
|
177
|
-
"value": "fig",
|
|
178
|
-
},
|
|
179
|
-
"oid": "test/a:0",
|
|
180
|
-
"timestamp": "0",
|
|
181
|
-
},
|
|
182
|
-
]
|
|
183
|
-
`);
|
|
184
|
-
});
|
|
185
|
-
it('retains keys which are undefined in new object if defaultUndefined is set', () => {
|
|
186
|
-
const from = {
|
|
187
|
-
foo: 'bar',
|
|
188
|
-
baz: { qux: 'corge' },
|
|
189
|
-
};
|
|
190
|
-
assignOid(from, 'test/a');
|
|
191
|
-
assignOid(from.baz, 'test/a:0');
|
|
192
|
-
const to = {
|
|
193
|
-
foo: 'bip',
|
|
194
|
-
};
|
|
195
|
-
assignOid(to, 'test/a');
|
|
196
|
-
const patches = diffToPatches(from, to, createClock(), undefined, [], {
|
|
197
|
-
defaultUndefined: true,
|
|
198
|
-
mergeUnknownObjects: true,
|
|
199
|
-
});
|
|
200
|
-
expect(patches).toMatchInlineSnapshot(`
|
|
201
|
-
[
|
|
202
|
-
{
|
|
203
|
-
"data": {
|
|
204
|
-
"name": "foo",
|
|
205
|
-
"op": "set",
|
|
206
|
-
"value": "bip",
|
|
207
|
-
},
|
|
208
|
-
"oid": "test/a",
|
|
209
|
-
"timestamp": "0",
|
|
210
|
-
},
|
|
211
|
-
]
|
|
212
|
-
`);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
14
|
+
describe('applying operations', () => {
|
|
215
15
|
describe('on lists of primitives', () => {
|
|
216
16
|
it('pushes items', async () => {
|
|
217
17
|
expect(applyOperations(assignOid(['foo', 'bar'], 'test/a'), [
|
|
@@ -225,407 +25,73 @@ describe('creating diff patch operations', () => {
|
|
|
225
25
|
},
|
|
226
26
|
])).toEqual(assignOid(['foo', 'bar', 'baz'], 'test/a'));
|
|
227
27
|
});
|
|
228
|
-
it
|
|
229
|
-
|
|
230
|
-
it.todo('removes items by index');
|
|
231
|
-
it.todo('removes items by value');
|
|
232
|
-
it.todo('moves items by index');
|
|
233
|
-
it.todo('moves items by value');
|
|
234
|
-
});
|
|
235
|
-
describe.todo('on lists of objects', () => {
|
|
236
|
-
it.todo('pushes items');
|
|
237
|
-
it.todo('moves objects by identity');
|
|
238
|
-
it.todo('removes objects by identity');
|
|
239
|
-
});
|
|
240
|
-
describe.todo('on lists of lists', () => {
|
|
241
|
-
it.todo('rejects operations by value or identity');
|
|
242
|
-
});
|
|
243
|
-
it('should work for complex nested arrays and objects', () => {
|
|
244
|
-
const from = {
|
|
245
|
-
foo: {
|
|
246
|
-
bar: [1, 2, 3],
|
|
247
|
-
},
|
|
248
|
-
baz: [
|
|
249
|
-
{
|
|
250
|
-
corge: true,
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
};
|
|
254
|
-
assignOid(from, 'test/a');
|
|
255
|
-
assignOid(from.foo, 'test/a:1');
|
|
256
|
-
assignOid(from.foo.bar, 'test/a:2');
|
|
257
|
-
assignOid(from.baz, 'test/a:3');
|
|
258
|
-
assignOid(from.baz[0], 'test/a:4');
|
|
259
|
-
const to = {
|
|
260
|
-
foo: {
|
|
261
|
-
bar: [1, 2],
|
|
262
|
-
bop: [0],
|
|
263
|
-
},
|
|
264
|
-
baz: [
|
|
28
|
+
it('sets items', () => {
|
|
29
|
+
expect(applyOperations(assignOid(['foo', 'bar'], 'test/a'), [
|
|
265
30
|
{
|
|
266
|
-
|
|
31
|
+
oid: 'test/a',
|
|
32
|
+
timestamp: 'meh',
|
|
33
|
+
data: {
|
|
34
|
+
op: 'list-set',
|
|
35
|
+
index: 1,
|
|
36
|
+
value: 'baz',
|
|
37
|
+
},
|
|
267
38
|
},
|
|
39
|
+
])).toEqual(assignOid(['foo', 'baz'], 'test/a'));
|
|
40
|
+
});
|
|
41
|
+
it('inserts items', () => {
|
|
42
|
+
expect(applyOperations(assignOid(['foo', 'bar'], 'test/a'), [
|
|
268
43
|
{
|
|
269
|
-
|
|
44
|
+
oid: 'test/a',
|
|
45
|
+
timestamp: 'meh',
|
|
46
|
+
data: {
|
|
47
|
+
op: 'list-insert',
|
|
48
|
+
index: 1,
|
|
49
|
+
values: ['baz'],
|
|
50
|
+
},
|
|
270
51
|
},
|
|
271
|
-
],
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
assignOid(to.foo.bar, 'test/a:2');
|
|
276
|
-
assignOid(to.foo.bop, 'test/a:6');
|
|
277
|
-
assignOid(to.baz, 'test/a:3');
|
|
278
|
-
assignOid(to.baz[0], 'test/a:4');
|
|
279
|
-
assignOid(to.baz[1], 'test/a:5');
|
|
280
|
-
expect(diffToPatches(from, to, createClock())).toMatchInlineSnapshot(`
|
|
281
|
-
[
|
|
282
|
-
{
|
|
283
|
-
"data": {
|
|
284
|
-
"count": 1,
|
|
285
|
-
"index": 2,
|
|
286
|
-
"op": "list-delete",
|
|
287
|
-
},
|
|
288
|
-
"oid": "test/a:2",
|
|
289
|
-
"timestamp": "0",
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
"data": {
|
|
293
|
-
"op": "initialize",
|
|
294
|
-
"value": [
|
|
295
|
-
0,
|
|
296
|
-
],
|
|
297
|
-
},
|
|
298
|
-
"oid": "test/a:6",
|
|
299
|
-
"timestamp": "1",
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
"data": {
|
|
303
|
-
"name": "bop",
|
|
304
|
-
"op": "set",
|
|
305
|
-
"value": {
|
|
306
|
-
"@@type": "ref",
|
|
307
|
-
"id": "test/a:6",
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
"oid": "test/a:1",
|
|
311
|
-
"timestamp": "2",
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
"data": {
|
|
315
|
-
"name": "corge",
|
|
316
|
-
"op": "set",
|
|
317
|
-
"value": false,
|
|
318
|
-
},
|
|
319
|
-
"oid": "test/a:4",
|
|
320
|
-
"timestamp": "3",
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
"data": {
|
|
324
|
-
"op": "initialize",
|
|
325
|
-
"value": {
|
|
326
|
-
"corge": false,
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
"oid": "test/a:5",
|
|
330
|
-
"timestamp": "4",
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
"data": {
|
|
334
|
-
"name": 1,
|
|
335
|
-
"op": "set",
|
|
336
|
-
"value": {
|
|
337
|
-
"@@type": "ref",
|
|
338
|
-
"id": "test/a:5",
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
"oid": "test/a:3",
|
|
342
|
-
"timestamp": "5",
|
|
343
|
-
},
|
|
344
|
-
]
|
|
345
|
-
`);
|
|
346
|
-
});
|
|
347
|
-
it('should try to merge unknown objects if specified to do so', () => {
|
|
348
|
-
const from = {
|
|
349
|
-
foo: {
|
|
350
|
-
bar: [1, 2, 3],
|
|
351
|
-
},
|
|
352
|
-
baz: [
|
|
52
|
+
])).toEqual(assignOid(['foo', 'baz', 'bar'], 'test/a'));
|
|
53
|
+
});
|
|
54
|
+
it('removes items by index', () => {
|
|
55
|
+
expect(applyOperations(assignOid(['foo', 'bar'], 'test/a'), [
|
|
353
56
|
{
|
|
354
|
-
|
|
57
|
+
oid: 'test/a',
|
|
58
|
+
timestamp: 'meh',
|
|
59
|
+
data: {
|
|
60
|
+
op: 'list-delete',
|
|
61
|
+
index: 1,
|
|
62
|
+
count: 1,
|
|
63
|
+
},
|
|
355
64
|
},
|
|
356
|
-
],
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const to = {
|
|
361
|
-
foo: {
|
|
362
|
-
bar: [1, 2],
|
|
363
|
-
bop: [0],
|
|
364
|
-
},
|
|
365
|
-
baz: [
|
|
65
|
+
])).toEqual(assignOid(['foo'], 'test/a'));
|
|
66
|
+
});
|
|
67
|
+
it('removes items by value', () => {
|
|
68
|
+
expect(applyOperations(assignOid(['bar', 'foo', 'bar'], 'test/a'), [
|
|
366
69
|
{
|
|
367
|
-
|
|
70
|
+
oid: 'test/a',
|
|
71
|
+
timestamp: 'meh',
|
|
72
|
+
data: {
|
|
73
|
+
op: 'list-remove',
|
|
74
|
+
value: 'bar',
|
|
75
|
+
},
|
|
368
76
|
},
|
|
77
|
+
])).toEqual(assignOid(['foo'], 'test/a'));
|
|
78
|
+
});
|
|
79
|
+
it('moves items by index', () => {
|
|
80
|
+
expect(applyOperations(assignOid(['foo', 'bar', 'baz'], 'test/a'), [
|
|
369
81
|
{
|
|
370
|
-
|
|
82
|
+
oid: 'test/a',
|
|
83
|
+
timestamp: 'meh',
|
|
84
|
+
data: {
|
|
85
|
+
op: 'list-move-by-index',
|
|
86
|
+
from: 2,
|
|
87
|
+
to: 0,
|
|
88
|
+
},
|
|
371
89
|
},
|
|
372
|
-
],
|
|
373
|
-
};
|
|
374
|
-
expect(diffToPatches(from, to, createClock(), createClock(5), [], {
|
|
375
|
-
mergeUnknownObjects: true,
|
|
376
|
-
})).toMatchInlineSnapshot(`
|
|
377
|
-
[
|
|
378
|
-
{
|
|
379
|
-
"data": {
|
|
380
|
-
"count": 1,
|
|
381
|
-
"index": 2,
|
|
382
|
-
"op": "list-delete",
|
|
383
|
-
},
|
|
384
|
-
"oid": "test/a:1",
|
|
385
|
-
"timestamp": "0",
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
"data": {
|
|
389
|
-
"op": "initialize",
|
|
390
|
-
"value": [
|
|
391
|
-
0,
|
|
392
|
-
],
|
|
393
|
-
},
|
|
394
|
-
"oid": "test/a:5",
|
|
395
|
-
"timestamp": "1",
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
"data": {
|
|
399
|
-
"name": "bop",
|
|
400
|
-
"op": "set",
|
|
401
|
-
"value": {
|
|
402
|
-
"@@type": "ref",
|
|
403
|
-
"id": "test/a:5",
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
"oid": "test/a:0",
|
|
407
|
-
"timestamp": "2",
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
"data": {
|
|
411
|
-
"name": "corge",
|
|
412
|
-
"op": "set",
|
|
413
|
-
"value": false,
|
|
414
|
-
},
|
|
415
|
-
"oid": "test/a:3",
|
|
416
|
-
"timestamp": "3",
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
"data": {
|
|
420
|
-
"op": "initialize",
|
|
421
|
-
"value": {
|
|
422
|
-
"corge": false,
|
|
423
|
-
},
|
|
424
|
-
},
|
|
425
|
-
"oid": "test/a:6",
|
|
426
|
-
"timestamp": "4",
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
"data": {
|
|
430
|
-
"name": 1,
|
|
431
|
-
"op": "set",
|
|
432
|
-
"value": {
|
|
433
|
-
"@@type": "ref",
|
|
434
|
-
"id": "test/a:6",
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
"oid": "test/a:2",
|
|
438
|
-
"timestamp": "5",
|
|
439
|
-
},
|
|
440
|
-
]
|
|
441
|
-
`);
|
|
442
|
-
});
|
|
443
|
-
it('should not diff file refs', () => {
|
|
444
|
-
const from = {
|
|
445
|
-
foo: {
|
|
446
|
-
file: createFileRef('abc123'),
|
|
447
|
-
},
|
|
448
|
-
bar: [createFileRef('def456'), createFileRef('ghi789')],
|
|
449
|
-
};
|
|
450
|
-
assignOid(from, 'test/a');
|
|
451
|
-
assignOidsToAllSubObjects(from, createClock());
|
|
452
|
-
const to = {
|
|
453
|
-
foo: {
|
|
454
|
-
file: createFileRef('abc456'),
|
|
455
|
-
},
|
|
456
|
-
bar: [createFileRef('def456')],
|
|
457
|
-
};
|
|
458
|
-
expect(diffToPatches(from, to, createClock(), createClock(5), [], {
|
|
459
|
-
mergeUnknownObjects: true,
|
|
460
|
-
})).toMatchInlineSnapshot(`
|
|
461
|
-
[
|
|
462
|
-
{
|
|
463
|
-
"data": {
|
|
464
|
-
"name": "file",
|
|
465
|
-
"op": "set",
|
|
466
|
-
"value": {
|
|
467
|
-
"@@type": "file",
|
|
468
|
-
"id": "abc456",
|
|
469
|
-
},
|
|
470
|
-
},
|
|
471
|
-
"oid": "test/a:0",
|
|
472
|
-
"timestamp": "0",
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
"data": {
|
|
476
|
-
"count": 1,
|
|
477
|
-
"index": 1,
|
|
478
|
-
"op": "list-delete",
|
|
479
|
-
},
|
|
480
|
-
"oid": "test/a:1",
|
|
481
|
-
"timestamp": "1",
|
|
482
|
-
},
|
|
483
|
-
]
|
|
484
|
-
`);
|
|
485
|
-
});
|
|
486
|
-
it('should assign a new OID to objects which had an incompatible existing OID', () => {
|
|
487
|
-
const from = {
|
|
488
|
-
foo: {
|
|
489
|
-
bar: [1, 2, 3],
|
|
490
|
-
},
|
|
491
|
-
};
|
|
492
|
-
assignOid(from, 'test/a');
|
|
493
|
-
assignOidsToAllSubObjects(from, createClock());
|
|
494
|
-
const to = {
|
|
495
|
-
foo: {
|
|
496
|
-
bar: [1, 2],
|
|
497
|
-
},
|
|
498
|
-
};
|
|
499
|
-
assignOid(to, 'test/a');
|
|
500
|
-
assignOid(to.foo, 'uff/b');
|
|
501
|
-
assignOidsToAllSubObjects(to.foo, createClock());
|
|
502
|
-
expect(diffToPatches(from, to, createClock(), createClock(5), [], {
|
|
503
|
-
mergeUnknownObjects: true,
|
|
504
|
-
})).toMatchInlineSnapshot(`
|
|
505
|
-
[
|
|
506
|
-
{
|
|
507
|
-
"data": {
|
|
508
|
-
"op": "initialize",
|
|
509
|
-
"value": [
|
|
510
|
-
1,
|
|
511
|
-
2,
|
|
512
|
-
],
|
|
513
|
-
},
|
|
514
|
-
"oid": "test/a:6",
|
|
515
|
-
"timestamp": "0",
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
"data": {
|
|
519
|
-
"op": "initialize",
|
|
520
|
-
"value": {
|
|
521
|
-
"bar": {
|
|
522
|
-
"@@type": "ref",
|
|
523
|
-
"id": "test/a:6",
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
},
|
|
527
|
-
"oid": "test/a:5",
|
|
528
|
-
"timestamp": "1",
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
"data": {
|
|
532
|
-
"name": "foo",
|
|
533
|
-
"op": "set",
|
|
534
|
-
"value": {
|
|
535
|
-
"@@type": "ref",
|
|
536
|
-
"id": "test/a:5",
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
"oid": "test/a",
|
|
540
|
-
"timestamp": "2",
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
"data": {
|
|
544
|
-
"op": "delete",
|
|
545
|
-
},
|
|
546
|
-
"oid": "test/a:0",
|
|
547
|
-
"timestamp": "3",
|
|
548
|
-
},
|
|
549
|
-
]
|
|
550
|
-
`);
|
|
90
|
+
])).toEqual(assignOid(['baz', 'foo', 'bar'], 'test/a'));
|
|
91
|
+
});
|
|
551
92
|
});
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
// in that tree have compatible OIDs.
|
|
555
|
-
it('should assign a new OID to objects which have an incompatible OID when parent identity changes', () => {
|
|
556
|
-
const from = {
|
|
557
|
-
bar: [],
|
|
558
|
-
};
|
|
559
|
-
assignOid(from, 'test/a');
|
|
560
|
-
assignOid(from.bar, 'test/random');
|
|
561
|
-
const to = {
|
|
562
|
-
bar: [{ baz: 1 }, { baz: 2 }],
|
|
563
|
-
};
|
|
564
|
-
assignOid(to, 'test/a');
|
|
565
|
-
assignOidsToAllSubObjects(to, createClock());
|
|
566
|
-
assignOid(to.bar[0], 'uff/b');
|
|
567
|
-
expect(diffToPatches(from, to, createClock(), createClock(5), [], {
|
|
568
|
-
mergeUnknownObjects: true,
|
|
569
|
-
})).toMatchInlineSnapshot(`
|
|
570
|
-
[
|
|
571
|
-
{
|
|
572
|
-
"data": {
|
|
573
|
-
"op": "initialize",
|
|
574
|
-
"value": {
|
|
575
|
-
"baz": 1,
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
"oid": "test/a:5",
|
|
579
|
-
"timestamp": "0",
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
"data": {
|
|
583
|
-
"op": "initialize",
|
|
584
|
-
"value": {
|
|
585
|
-
"baz": 2,
|
|
586
|
-
},
|
|
587
|
-
},
|
|
588
|
-
"oid": "test/a:2",
|
|
589
|
-
"timestamp": "1",
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
"data": {
|
|
593
|
-
"op": "initialize",
|
|
594
|
-
"value": [
|
|
595
|
-
{
|
|
596
|
-
"@@type": "ref",
|
|
597
|
-
"id": "test/a:5",
|
|
598
|
-
},
|
|
599
|
-
{
|
|
600
|
-
"@@type": "ref",
|
|
601
|
-
"id": "test/a:2",
|
|
602
|
-
},
|
|
603
|
-
],
|
|
604
|
-
},
|
|
605
|
-
"oid": "test/a:0",
|
|
606
|
-
"timestamp": "2",
|
|
607
|
-
},
|
|
608
|
-
{
|
|
609
|
-
"data": {
|
|
610
|
-
"name": "bar",
|
|
611
|
-
"op": "set",
|
|
612
|
-
"value": {
|
|
613
|
-
"@@type": "ref",
|
|
614
|
-
"id": "test/a:0",
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
"oid": "test/a",
|
|
618
|
-
"timestamp": "3",
|
|
619
|
-
},
|
|
620
|
-
{
|
|
621
|
-
"data": {
|
|
622
|
-
"op": "delete",
|
|
623
|
-
},
|
|
624
|
-
"oid": "test/random",
|
|
625
|
-
"timestamp": "4",
|
|
626
|
-
},
|
|
627
|
-
]
|
|
628
|
-
`);
|
|
93
|
+
describe.todo('on lists of lists', () => {
|
|
94
|
+
it.todo('rejects operations by value or identity');
|
|
629
95
|
});
|
|
630
96
|
});
|
|
631
97
|
describe('substituting refs with objects', () => {
|
|
@@ -752,6 +218,70 @@ describe('substituting refs with objects', () => {
|
|
|
752
218
|
});
|
|
753
219
|
it.todo('substitutes arrays of arrays of objects');
|
|
754
220
|
});
|
|
221
|
+
describe('substituting objects with refs', () => {
|
|
222
|
+
it('deconstructs nested objects and arrays properly', () => {
|
|
223
|
+
const snapshot = assignOid({
|
|
224
|
+
foo: {
|
|
225
|
+
bar: {
|
|
226
|
+
baz: [{ corge: 'qux' }, { grault: 'garply' }],
|
|
227
|
+
},
|
|
228
|
+
prim: 'hi',
|
|
229
|
+
},
|
|
230
|
+
cat: {
|
|
231
|
+
percol: 'simmi',
|
|
232
|
+
},
|
|
233
|
+
}, 'test/a');
|
|
234
|
+
assignOidsToAllSubObjects(snapshot, createClock());
|
|
235
|
+
const deconstructed = new Map();
|
|
236
|
+
deconstructSnapshotToRefs(snapshot, deconstructed);
|
|
237
|
+
expect(deconstructed).toMatchInlineSnapshot(`
|
|
238
|
+
Map {
|
|
239
|
+
"test/a" => {
|
|
240
|
+
"cat": {
|
|
241
|
+
"@@type": "ref",
|
|
242
|
+
"id": "test/a:5",
|
|
243
|
+
},
|
|
244
|
+
"foo": {
|
|
245
|
+
"@@type": "ref",
|
|
246
|
+
"id": "test/a:0",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
"test/a:0" => {
|
|
250
|
+
"bar": {
|
|
251
|
+
"@@type": "ref",
|
|
252
|
+
"id": "test/a:1",
|
|
253
|
+
},
|
|
254
|
+
"prim": "hi",
|
|
255
|
+
},
|
|
256
|
+
"test/a:1" => {
|
|
257
|
+
"baz": {
|
|
258
|
+
"@@type": "ref",
|
|
259
|
+
"id": "test/a:2",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
"test/a:2" => [
|
|
263
|
+
{
|
|
264
|
+
"@@type": "ref",
|
|
265
|
+
"id": "test/a:3",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"@@type": "ref",
|
|
269
|
+
"id": "test/a:4",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
"test/a:3" => {
|
|
273
|
+
"corge": "qux",
|
|
274
|
+
},
|
|
275
|
+
"test/a:4" => {
|
|
276
|
+
"grault": "garply",
|
|
277
|
+
},
|
|
278
|
+
"test/a:5" => {
|
|
279
|
+
"percol": "simmi",
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
`);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
755
285
|
describe('creating patches from initial state', () => {
|
|
756
286
|
it('adds oids to all sub-objects', async () => {
|
|
757
287
|
let i = 0;
|