@isograph/react 0.0.0-main-3f26c9c8 → 0.0.0-main-84b67b62

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 (41) hide show
  1. package/.turbo/turbo-compile-libs.log +1 -1
  2. package/dist/core/IsographEnvironment.d.ts +9 -6
  3. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  4. package/dist/core/IsographEnvironment.js +7 -1
  5. package/dist/core/cache.d.ts +2 -1
  6. package/dist/core/cache.d.ts.map +1 -1
  7. package/dist/core/cache.js +17 -27
  8. package/dist/core/check.d.ts.map +1 -1
  9. package/dist/core/check.js +10 -7
  10. package/dist/core/garbageCollection.d.ts +2 -1
  11. package/dist/core/garbageCollection.d.ts.map +1 -1
  12. package/dist/core/garbageCollection.js +21 -13
  13. package/dist/core/logging.d.ts +3 -2
  14. package/dist/core/logging.d.ts.map +1 -1
  15. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  16. package/dist/core/makeNetworkRequest.js +10 -1
  17. package/dist/core/optimisticProxy.d.ts +51 -0
  18. package/dist/core/optimisticProxy.d.ts.map +1 -0
  19. package/dist/core/optimisticProxy.js +372 -0
  20. package/dist/core/read.d.ts.map +1 -1
  21. package/dist/core/read.js +5 -4
  22. package/dist/core/startUpdate.d.ts +2 -1
  23. package/dist/core/startUpdate.d.ts.map +1 -1
  24. package/dist/core/startUpdate.js +31 -32
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/package.json +4 -4
  28. package/src/core/IsographEnvironment.ts +16 -6
  29. package/src/core/cache.ts +21 -14
  30. package/src/core/check.ts +11 -6
  31. package/src/core/garbageCollection.ts +27 -15
  32. package/src/core/logging.ts +2 -2
  33. package/src/core/makeNetworkRequest.ts +14 -1
  34. package/src/core/optimisticProxy.ts +510 -0
  35. package/src/core/read.ts +2 -1
  36. package/src/core/startUpdate.ts +44 -28
  37. package/src/index.ts +1 -1
  38. package/src/tests/garbageCollection.test.ts +2 -2
  39. package/src/tests/normalizeData.test.ts +5 -3
  40. package/src/tests/optimisticProxy.test.ts +860 -0
  41. package/src/tests/startUpdate.test.ts +7 -5
@@ -0,0 +1,860 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { callSubscriptions, type EncounteredIds } from '../core/cache';
3
+ import {
4
+ createIsographEnvironment,
5
+ createIsographStore,
6
+ type IsographEnvironment,
7
+ type StoreLayerData,
8
+ } from '../core/IsographEnvironment';
9
+ import {
10
+ addNetworkResponseStoreLayer as addNetworkResponseStoreLayerInner,
11
+ addOptimisticStoreLayer as addOptimisticStoreLayerInner,
12
+ addStartUpdateStoreLayer as addStartUpdateStoreLayerInner,
13
+ getStoreRecordProxy,
14
+ revertOptimisticStoreLayerAndMaybeReplace,
15
+ type OptimisticStoreLayer,
16
+ type StoreLayer,
17
+ } from '../core/optimisticProxy';
18
+
19
+ vi.mock(import('../core/cache'), { spy: true });
20
+
21
+ const CHANGES = new Map([['Query', new Set(['__ROOT'])]]);
22
+ const NO_CHANGES = new Map();
23
+
24
+ describe('optimisticLayer', () => {
25
+ let environment: ReturnType<typeof createIsographEnvironment>;
26
+
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ const networkFunction = vi
30
+ .fn()
31
+ .mockRejectedValue(new Error('Fetch failed'));
32
+ environment = createIsographEnvironment(
33
+ createIsographStore(),
34
+ networkFunction,
35
+ );
36
+ addNetworkResponseStoreLayer(environment, 0);
37
+ });
38
+
39
+ describe('addNetworkResponseStoreLayer', () => {
40
+ test('has parent BaseStoreLayer', () => {
41
+ expect(environment.store).toMatchObject({
42
+ kind: 'BaseStoreLayer',
43
+ });
44
+ });
45
+
46
+ test('merge', () => {
47
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
48
+ addNetworkResponseStoreLayer(environment, 3);
49
+ addNetworkResponseStoreLayer(environment, 4);
50
+
51
+ expect(environment.store).toMatchObject({
52
+ kind: 'NetworkResponseStoreLayer',
53
+ parentStoreLayer: {
54
+ kind: 'OptimisticStoreLayer',
55
+ parentStoreLayer: {
56
+ kind: 'BaseStoreLayer',
57
+ },
58
+ },
59
+ });
60
+
61
+ expect(
62
+ getStoreRecordProxy(environment.store, {
63
+ __link: '__ROOT',
64
+ __typename: 'Query',
65
+ })?.counter,
66
+ ).toBe(4);
67
+ });
68
+ });
69
+
70
+ describe('addStartUpdateStoreLayer', () => {
71
+ test('merge', () => {
72
+ addOptimisticStoreLayer(environment, () => 1);
73
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
74
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
75
+
76
+ expect(environment.store).toMatchObject({
77
+ kind: 'StartUpdateStoreLayer',
78
+ parentStoreLayer: {
79
+ kind: 'OptimisticStoreLayer',
80
+ parentStoreLayer: {
81
+ kind: 'BaseStoreLayer',
82
+ },
83
+ },
84
+ });
85
+ expect(
86
+ getStoreRecordProxy(environment.store, {
87
+ __link: '__ROOT',
88
+ __typename: 'Query',
89
+ })?.counter,
90
+ ).toBe(3);
91
+ });
92
+ });
93
+
94
+ describe('addOptimisticStoreLayer', () => {
95
+ test('add tree nodes', () => {
96
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
97
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
98
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
99
+
100
+ expect(
101
+ getStoreRecordProxy(environment.store, {
102
+ __link: '__ROOT',
103
+ __typename: 'Query',
104
+ })?.counter,
105
+ ).toBe(3);
106
+ });
107
+ });
108
+
109
+ describe('replaceOptimisticStoreLayerWithNetworkResponseStoreLayer', () => {
110
+ describe('subscriptions', () => {
111
+ test('calls if network response differs', () => {
112
+ const revert = addOptimisticStoreLayer(environment, () => 1);
113
+
114
+ revert(2);
115
+
116
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
117
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
118
+ 1,
119
+ expect.anything(),
120
+ CHANGES,
121
+ );
122
+ });
123
+
124
+ test("doesn't call subscriptions if no changes", () => {
125
+ const revert = addOptimisticStoreLayer(environment, () => 1);
126
+
127
+ revert(1);
128
+
129
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
130
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
131
+ 1,
132
+ expect.anything(),
133
+ NO_CHANGES,
134
+ );
135
+ });
136
+
137
+ test('calls subscriptions if nodes update different fields', () => {
138
+ const node = addOptimisticStoreLayerInner(
139
+ environment.store,
140
+ (storeLayer) => {
141
+ ignoreReadonly(storeLayer).data = {
142
+ Query: {
143
+ __ROOT: {
144
+ surname: 'foo',
145
+ },
146
+ },
147
+ };
148
+ return CHANGES;
149
+ },
150
+ );
151
+ environment.store = node;
152
+ environment.store = addNetworkResponseStoreLayerInner(
153
+ environment.store,
154
+ );
155
+ ignoreReadonly(environment.store).data = {
156
+ Query: {
157
+ __ROOT: {
158
+ name: 'foo',
159
+ },
160
+ },
161
+ };
162
+
163
+ revertOptimisticStoreLayerAndMaybeReplace(
164
+ environment,
165
+ node,
166
+ (storeLayer) => {
167
+ ignoreReadonly(storeLayer).data = {
168
+ Query: { __ROOT: { surname: 'bar' } },
169
+ };
170
+ return CHANGES;
171
+ },
172
+ );
173
+
174
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
175
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
176
+ 1,
177
+ expect.anything(),
178
+ CHANGES,
179
+ );
180
+ });
181
+
182
+ test('calls subscriptions if reverted unrelated field', () => {
183
+ const node = addOptimisticStoreLayerInner(
184
+ environment.store,
185
+ (storeLayer) => {
186
+ ignoreReadonly(storeLayer).data = {
187
+ Pet: { 1: { surname: 'foo' } },
188
+ };
189
+ return new Map([['Pet', new Set(['1'])]]);
190
+ },
191
+ );
192
+ environment.store = node;
193
+
194
+ environment.store = addNetworkResponseStoreLayerInner(
195
+ environment.store,
196
+ );
197
+ ignoreReadonly(environment.store).data = {
198
+ Query: { __ROOT: { name: 'foo' } },
199
+ };
200
+
201
+ revertOptimisticStoreLayerAndMaybeReplace(
202
+ environment,
203
+ node,
204
+ (storeLayer) => {
205
+ ignoreReadonly(storeLayer).data = {};
206
+ return new Map();
207
+ },
208
+ );
209
+
210
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
211
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
212
+ 1,
213
+ expect.anything(),
214
+ new Map([['Pet', new Set(['1'])]]),
215
+ );
216
+ });
217
+ });
218
+
219
+ describe('with parent BaseStoreLayer', () => {
220
+ test('merges ', () => {
221
+ const revert = addOptimisticStoreLayer(
222
+ environment,
223
+ (counter) => counter + 1,
224
+ );
225
+
226
+ revert(5);
227
+
228
+ expect(environment.store).toMatchObject({
229
+ kind: 'BaseStoreLayer',
230
+ });
231
+
232
+ expect(
233
+ getStoreRecordProxy(environment.store, {
234
+ __link: '__ROOT',
235
+ __typename: 'Query',
236
+ })?.counter,
237
+ ).toBe(5);
238
+ });
239
+
240
+ test('merges children', () => {
241
+ const revert = addOptimisticStoreLayer(
242
+ environment,
243
+ (counter) => counter + 1,
244
+ );
245
+ addNetworkResponseStoreLayer(environment, 12);
246
+
247
+ revert(5);
248
+
249
+ expect(environment.store).toMatchObject({
250
+ kind: 'BaseStoreLayer',
251
+ });
252
+
253
+ expect(
254
+ getStoreRecordProxy(environment.store, {
255
+ __link: '__ROOT',
256
+ __typename: 'Query',
257
+ })?.counter,
258
+ ).toBe(12);
259
+ });
260
+
261
+ test('merges children and stops at optimistic child node', () => {
262
+ const revert = addOptimisticStoreLayer(
263
+ environment,
264
+ (counter) => counter + 1,
265
+ );
266
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
267
+ addNetworkResponseStoreLayer(environment, 12);
268
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
269
+
270
+ revert(4);
271
+
272
+ expect(environment.store).toMatchObject({
273
+ kind: 'OptimisticStoreLayer',
274
+ parentStoreLayer: {
275
+ kind: 'BaseStoreLayer',
276
+ },
277
+ });
278
+ expect(
279
+ getStoreRecordProxy(environment.store, {
280
+ __link: '__ROOT',
281
+ __typename: 'Query',
282
+ })?.counter,
283
+ ).toBe(13);
284
+ });
285
+
286
+ test('reexecutes updates while merging children', () => {
287
+ const revert = addOptimisticStoreLayer(
288
+ environment,
289
+ (counter) => counter + 1,
290
+ );
291
+ addStartUpdateStoreLayer(environment, (counter) => counter * 2);
292
+ addStartUpdateStoreLayer(environment, (counter) => counter + 7);
293
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
294
+
295
+ revert(4);
296
+
297
+ expect(environment.store).toMatchObject({
298
+ kind: 'OptimisticStoreLayer',
299
+ parentStoreLayer: {
300
+ kind: 'BaseStoreLayer',
301
+ },
302
+ });
303
+ expect(
304
+ getStoreRecordProxy(environment.store, {
305
+ __link: '__ROOT',
306
+ __typename: 'Query',
307
+ })?.counter,
308
+ ).toBe(16);
309
+ });
310
+
311
+ test('stops at optimistic child node', () => {
312
+ const revert = addOptimisticStoreLayer(
313
+ environment,
314
+ (counter) => counter + 1,
315
+ );
316
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
317
+
318
+ revert(5);
319
+
320
+ expect(environment.store).toMatchObject({
321
+ kind: 'OptimisticStoreLayer',
322
+ parentStoreLayer: {
323
+ kind: 'BaseStoreLayer',
324
+ },
325
+ });
326
+
327
+ expect(
328
+ getStoreRecordProxy(environment.store, {
329
+ __link: '__ROOT',
330
+ __typename: 'Query',
331
+ })?.counter,
332
+ ).toBe(6);
333
+ });
334
+ });
335
+
336
+ describe('adjacent with NetworkResponseStoreLayer', () => {
337
+ test('merges with parent NetworkResponseStoreLayer', () => {
338
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
339
+ addNetworkResponseStoreLayer(environment, 10);
340
+ const revert = addOptimisticStoreLayer(
341
+ environment,
342
+ (counter) => counter + 1,
343
+ );
344
+ addNetworkResponseStoreLayer(environment, 12);
345
+ revert(5);
346
+
347
+ expect(environment.store).toMatchObject({
348
+ kind: 'NetworkResponseStoreLayer',
349
+ parentStoreLayer: {
350
+ kind: 'OptimisticStoreLayer',
351
+ parentStoreLayer: {
352
+ kind: 'BaseStoreLayer',
353
+ },
354
+ },
355
+ });
356
+
357
+ expect(
358
+ getStoreRecordProxy(environment.store, {
359
+ __link: '__ROOT',
360
+ __typename: 'Query',
361
+ })?.counter,
362
+ ).toBe(12);
363
+ });
364
+
365
+ test('merges child NetworkResponseStoreLayer', () => {
366
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
367
+ const revert = addOptimisticStoreLayer(
368
+ environment,
369
+ (counter) => counter + 1,
370
+ );
371
+ addNetworkResponseStoreLayer(environment, 12);
372
+ revert(5);
373
+
374
+ expect(environment.store).toMatchObject({
375
+ kind: 'NetworkResponseStoreLayer',
376
+ parentStoreLayer: {
377
+ kind: 'OptimisticStoreLayer',
378
+ parentStoreLayer: {
379
+ kind: 'BaseStoreLayer',
380
+ },
381
+ },
382
+ });
383
+
384
+ expect(
385
+ getStoreRecordProxy(environment.store, {
386
+ __link: '__ROOT',
387
+ __typename: 'Query',
388
+ })?.counter,
389
+ ).toBe(12);
390
+ });
391
+
392
+ test('merges between two NetworkResponseStoreLayers', () => {
393
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
394
+ addNetworkResponseStoreLayer(environment, 10);
395
+ const revert = addOptimisticStoreLayer(
396
+ environment,
397
+ (counter) => counter + 1,
398
+ );
399
+ addNetworkResponseStoreLayer(environment, 12);
400
+ revert(5);
401
+
402
+ expect(environment.store).toMatchObject({
403
+ kind: 'NetworkResponseStoreLayer',
404
+ parentStoreLayer: {
405
+ kind: 'OptimisticStoreLayer',
406
+ parentStoreLayer: {
407
+ kind: 'BaseStoreLayer',
408
+ },
409
+ },
410
+ });
411
+
412
+ expect(
413
+ getStoreRecordProxy(environment.store, {
414
+ __link: '__ROOT',
415
+ __typename: 'Query',
416
+ })?.counter,
417
+ ).toBe(12);
418
+ });
419
+ });
420
+
421
+ describe('has parent OptimisticStoreLayer', () => {
422
+ test('replaces self with network response', () => {
423
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
424
+ const revert = addOptimisticStoreLayer(
425
+ environment,
426
+ (counter) => counter + 1,
427
+ );
428
+
429
+ revert(5);
430
+
431
+ expect(environment.store).toMatchObject({
432
+ kind: 'NetworkResponseStoreLayer',
433
+ parentStoreLayer: {
434
+ kind: 'OptimisticStoreLayer',
435
+ parentStoreLayer: {
436
+ kind: 'BaseStoreLayer',
437
+ },
438
+ },
439
+ });
440
+
441
+ expect(
442
+ getStoreRecordProxy(environment.store, {
443
+ __link: '__ROOT',
444
+ __typename: 'Query',
445
+ })?.counter,
446
+ ).toBe(5);
447
+ });
448
+ test("doesn't merge child nodes if has parent nodes", () => {
449
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
450
+ const revert = addOptimisticStoreLayer(
451
+ environment,
452
+ (counter) => counter + 1,
453
+ );
454
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
455
+
456
+ revert(5);
457
+
458
+ expect(environment.store).toMatchObject({
459
+ kind: 'StartUpdateStoreLayer',
460
+ parentStoreLayer: {
461
+ kind: 'NetworkResponseStoreLayer',
462
+ parentStoreLayer: {
463
+ kind: 'OptimisticStoreLayer',
464
+ parentStoreLayer: {
465
+ kind: 'BaseStoreLayer',
466
+ },
467
+ },
468
+ },
469
+ });
470
+
471
+ expect(
472
+ getStoreRecordProxy(environment.store, {
473
+ __link: '__ROOT',
474
+ __typename: 'Query',
475
+ })?.counter,
476
+ ).toBe(6);
477
+ });
478
+ });
479
+
480
+ describe('network error', () => {
481
+ describe('subscriptions', () => {
482
+ test('calls if network response differs', () => {
483
+ const revert = addOptimisticStoreLayer(environment, () => 1);
484
+
485
+ revert(null);
486
+
487
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
488
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
489
+ 1,
490
+ expect.anything(),
491
+ CHANGES,
492
+ );
493
+ });
494
+
495
+ test('calls subscriptions if reverted unrelated field', () => {
496
+ const node = addOptimisticStoreLayerInner(
497
+ environment.store,
498
+ (storeLayer) => {
499
+ ignoreReadonly(storeLayer).data = {
500
+ Pet: { 1: { surname: 'foo' } },
501
+ };
502
+ return new Map([['Pet', new Set(['1'])]]);
503
+ },
504
+ );
505
+ environment.store = node;
506
+
507
+ environment.store = addNetworkResponseStoreLayerInner(
508
+ environment.store,
509
+ );
510
+ ignoreReadonly(environment.store).data = {
511
+ Query: { __ROOT: { name: 'foo' } },
512
+ };
513
+
514
+ revert(environment, node, null);
515
+
516
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
517
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
518
+ 1,
519
+ expect.anything(),
520
+ new Map([['Pet', new Set(['1'])]]),
521
+ );
522
+ });
523
+ });
524
+
525
+ describe('with parent BaseStoreLayer', () => {
526
+ test('removes self ', () => {
527
+ const revert = addOptimisticStoreLayer(
528
+ environment,
529
+ (counter) => counter + 1,
530
+ );
531
+
532
+ revert(null);
533
+
534
+ expect(environment.store).toMatchObject({
535
+ kind: 'BaseStoreLayer',
536
+ });
537
+
538
+ expect(
539
+ getStoreRecordProxy(environment.store, {
540
+ __link: '__ROOT',
541
+ __typename: 'Query',
542
+ })?.counter,
543
+ ).toBe(0);
544
+ });
545
+
546
+ test('merges children', () => {
547
+ const revert = addOptimisticStoreLayer(
548
+ environment,
549
+ (counter) => counter + 1,
550
+ );
551
+ addNetworkResponseStoreLayer(environment, 12);
552
+
553
+ revert(null);
554
+
555
+ expect(environment.store).toMatchObject({
556
+ kind: 'BaseStoreLayer',
557
+ });
558
+
559
+ expect(
560
+ getStoreRecordProxy(environment.store, {
561
+ __link: '__ROOT',
562
+ __typename: 'Query',
563
+ })?.counter,
564
+ ).toBe(12);
565
+ });
566
+
567
+ test('merges children and stops at optimistic child node', () => {
568
+ const revert = addOptimisticStoreLayer(
569
+ environment,
570
+ (counter) => counter + 1,
571
+ );
572
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
573
+ addNetworkResponseStoreLayer(environment, 12);
574
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
575
+
576
+ revert(null);
577
+
578
+ expect(environment.store).toMatchObject({
579
+ kind: 'OptimisticStoreLayer',
580
+ parentStoreLayer: {
581
+ kind: 'BaseStoreLayer',
582
+ },
583
+ });
584
+ expect(
585
+ getStoreRecordProxy(environment.store, {
586
+ __link: '__ROOT',
587
+ __typename: 'Query',
588
+ })?.counter,
589
+ ).toBe(13);
590
+ });
591
+
592
+ test('reexecutes updates while merging children', () => {
593
+ const revert = addOptimisticStoreLayer(
594
+ environment,
595
+ (counter) => counter + 1,
596
+ );
597
+ addStartUpdateStoreLayer(environment, (counter) => counter + 2);
598
+ addStartUpdateStoreLayer(environment, (counter) => counter * 7);
599
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
600
+
601
+ revert(null);
602
+
603
+ expect(environment.store).toMatchObject({
604
+ kind: 'OptimisticStoreLayer',
605
+ parentStoreLayer: {
606
+ kind: 'BaseStoreLayer',
607
+ },
608
+ });
609
+ expect(
610
+ getStoreRecordProxy(environment.store, {
611
+ __link: '__ROOT',
612
+ __typename: 'Query',
613
+ })?.counter,
614
+ ).toBe(15);
615
+ });
616
+
617
+ test('stops at optimistic child node', () => {
618
+ const revert = addOptimisticStoreLayer(
619
+ environment,
620
+ (counter) => counter + 1,
621
+ );
622
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
623
+
624
+ revert(null);
625
+
626
+ expect(environment.store).toMatchObject({
627
+ kind: 'OptimisticStoreLayer',
628
+ parentStoreLayer: {
629
+ kind: 'BaseStoreLayer',
630
+ },
631
+ });
632
+
633
+ expect(
634
+ getStoreRecordProxy(environment.store, {
635
+ __link: '__ROOT',
636
+ __typename: 'Query',
637
+ })?.counter,
638
+ ).toBe(1);
639
+ });
640
+ });
641
+
642
+ describe('adjacent with NetworkResponseStoreLayer', () => {
643
+ test('merges with parent NetworkResponseStoreLayer', () => {
644
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
645
+ addNetworkResponseStoreLayer(environment, 10);
646
+ const revert = addOptimisticStoreLayer(
647
+ environment,
648
+ (counter) => counter + 1,
649
+ );
650
+ addNetworkResponseStoreLayer(environment, 12);
651
+ revert(null);
652
+
653
+ expect(environment.store).toMatchObject({
654
+ kind: 'NetworkResponseStoreLayer',
655
+ parentStoreLayer: {
656
+ kind: 'OptimisticStoreLayer',
657
+ parentStoreLayer: {
658
+ kind: 'BaseStoreLayer',
659
+ },
660
+ },
661
+ });
662
+
663
+ expect(
664
+ getStoreRecordProxy(environment.store, {
665
+ __link: '__ROOT',
666
+ __typename: 'Query',
667
+ })?.counter,
668
+ ).toBe(12);
669
+ });
670
+
671
+ test('merges child NetworkResponseStoreLayer', () => {
672
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
673
+ const revert = addOptimisticStoreLayer(
674
+ environment,
675
+ (counter) => counter + 1,
676
+ );
677
+ addNetworkResponseStoreLayer(environment, 12);
678
+ revert(null);
679
+
680
+ expect(environment.store).toMatchObject({
681
+ kind: 'NetworkResponseStoreLayer',
682
+ parentStoreLayer: {
683
+ kind: 'OptimisticStoreLayer',
684
+ parentStoreLayer: {
685
+ kind: 'BaseStoreLayer',
686
+ },
687
+ },
688
+ });
689
+
690
+ expect(
691
+ getStoreRecordProxy(environment.store, {
692
+ __link: '__ROOT',
693
+ __typename: 'Query',
694
+ })?.counter,
695
+ ).toBe(12);
696
+ });
697
+
698
+ test('removes self and merges two adjacent NetworkResponseStoreLayers', () => {
699
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
700
+ addNetworkResponseStoreLayer(environment, 10);
701
+ const revert = addOptimisticStoreLayer(
702
+ environment,
703
+ (counter) => counter + 1,
704
+ );
705
+ addNetworkResponseStoreLayer(environment, 12);
706
+ revert(null);
707
+
708
+ expect(environment.store).toMatchObject({
709
+ kind: 'NetworkResponseStoreLayer',
710
+ parentStoreLayer: {
711
+ kind: 'OptimisticStoreLayer',
712
+ parentStoreLayer: {
713
+ kind: 'BaseStoreLayer',
714
+ },
715
+ },
716
+ });
717
+
718
+ expect(
719
+ getStoreRecordProxy(environment.store, {
720
+ __link: '__ROOT',
721
+ __typename: 'Query',
722
+ })?.counter,
723
+ ).toBe(12);
724
+ });
725
+ });
726
+
727
+ describe('has parent OptimisticStoreLayer', () => {
728
+ test('removes self', () => {
729
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
730
+ const revert = addOptimisticStoreLayer(
731
+ environment,
732
+ (counter) => counter + 1,
733
+ );
734
+
735
+ revert(null);
736
+
737
+ expect(environment.store).toMatchObject({
738
+ kind: 'OptimisticStoreLayer',
739
+ parentStoreLayer: {
740
+ kind: 'BaseStoreLayer',
741
+ },
742
+ });
743
+
744
+ expect(
745
+ getStoreRecordProxy(environment.store, {
746
+ __link: '__ROOT',
747
+ __typename: 'Query',
748
+ })?.counter,
749
+ ).toBe(1);
750
+ });
751
+
752
+ test("doesn't merge child nodes if has parent nodes", () => {
753
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
754
+ const revert = addOptimisticStoreLayer(
755
+ environment,
756
+ (counter) => counter + 1,
757
+ );
758
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
759
+
760
+ revert(null);
761
+
762
+ expect(environment.store).toMatchObject({
763
+ kind: 'StartUpdateStoreLayer',
764
+ parentStoreLayer: {
765
+ kind: 'OptimisticStoreLayer',
766
+ parentStoreLayer: {
767
+ kind: 'BaseStoreLayer',
768
+ },
769
+ },
770
+ });
771
+
772
+ expect(
773
+ getStoreRecordProxy(environment.store, {
774
+ __link: '__ROOT',
775
+ __typename: 'Query',
776
+ })?.counter,
777
+ ).toBe(2);
778
+ });
779
+ });
780
+ });
781
+ });
782
+
783
+ // utils
784
+ function addNetworkResponseStoreLayer(
785
+ environment: IsographEnvironment,
786
+ counter: number,
787
+ ) {
788
+ environment.store = addNetworkResponseStoreLayerInner(environment.store);
789
+ update(environment.store, () => counter);
790
+ }
791
+
792
+ function addOptimisticStoreLayer(
793
+ environment: IsographEnvironment,
794
+ updater: (prev: number) => number,
795
+ ) {
796
+ const node = addOptimisticStoreLayerInner(
797
+ environment.store,
798
+ (storeLayer) => {
799
+ return update(storeLayer, updater);
800
+ },
801
+ );
802
+ environment.store = node;
803
+ return (counter: null | number) => {
804
+ revert(environment, node, counter);
805
+ };
806
+ }
807
+
808
+ function revert(
809
+ environment: IsographEnvironment,
810
+ node: OptimisticStoreLayer,
811
+ counter: null | number,
812
+ ) {
813
+ return revertOptimisticStoreLayerAndMaybeReplace(
814
+ environment,
815
+ node,
816
+ counter === null
817
+ ? counter
818
+ : (storeLayer) => update(storeLayer, () => counter),
819
+ );
820
+ }
821
+
822
+ function addStartUpdateStoreLayer(
823
+ environment: IsographEnvironment,
824
+ updater: (prev: number) => number,
825
+ ) {
826
+ const node = addStartUpdateStoreLayerInner(
827
+ environment.store,
828
+ (storeLayer) => {
829
+ return update(storeLayer, updater);
830
+ },
831
+ );
832
+ environment.store = node;
833
+ }
834
+
835
+ const update = (
836
+ storeLayer: StoreLayer,
837
+ value: (counter: number) => number,
838
+ ): EncounteredIds => {
839
+ const { counter } =
840
+ getStoreRecordProxy(storeLayer, {
841
+ __link: '__ROOT',
842
+ __typename: 'Query',
843
+ }) ?? {};
844
+ const nextCounter = value(Number(counter));
845
+
846
+ ignoreReadonly(storeLayer).data = {
847
+ Query: {
848
+ __ROOT: {
849
+ counter: nextCounter,
850
+ },
851
+ },
852
+ };
853
+
854
+ return counter != nextCounter ? CHANGES : NO_CHANGES;
855
+ };
856
+
857
+ function ignoreReadonly(value: StoreLayer): { data: StoreLayerData } {
858
+ return value;
859
+ }
860
+ });