Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/client/test/ObjectObserver.arrays.test.ts
1028 views
1
/**
2
ISC License (ISC)
3
4
Copyright 2015 Yuri Guller ([email protected])
5
Modifications 2021 Data Liberation Foundation
6
7
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,
8
provided that the above copyright notice and this permission notice appear in all copies.
9
10
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
11
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
12
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
13
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
14
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
*/
16
import ObjectObserver from '../lib/ObjectObserver';
17
18
test('array push - primitives', () => {
19
const events = [];
20
let callBacks = 0;
21
const pa = ObjectObserver.create([1, 2, 3, 4], changes => {
22
events.push(...changes);
23
callBacks += 1;
24
});
25
26
pa.push(5);
27
pa.push(6, 7);
28
29
expect(events).toHaveLength(3);
30
expect(callBacks).toBe(2);
31
expect(events[0]).toEqual({
32
type: 'insert',
33
path: [4],
34
value: 5,
35
});
36
expect(events[1]).toEqual({
37
type: 'insert',
38
path: [5],
39
value: 6,
40
});
41
expect(events[2]).toEqual({
42
type: 'insert',
43
path: [6],
44
value: 7,
45
});
46
});
47
48
test('array push - objects', () => {
49
const events = [];
50
const pa = ObjectObserver.create([], changes => events.push(...changes));
51
52
pa.push({ text: 'initial' }, { text: 'secondary' });
53
expect(events).toHaveLength(2);
54
expect(events[0]).toEqual({
55
type: 'insert',
56
path: [0],
57
value: { text: 'initial' },
58
});
59
expect(events[1]).toEqual({
60
type: 'insert',
61
path: [1],
62
value: { text: 'secondary' },
63
});
64
pa[0].text = 'name';
65
expect(events).toHaveLength(3);
66
expect(events[2]).toEqual({
67
type: 'update',
68
path: [0, 'text'],
69
value: 'name',
70
});
71
72
pa[1].text = 'more';
73
expect(events).toHaveLength(4);
74
expect(events[3]).toEqual({
75
type: 'update',
76
path: [1, 'text'],
77
value: 'more',
78
});
79
});
80
81
test('array push - arrays', () => {
82
const events = [];
83
const pa = ObjectObserver.create([], changes => events.push(...changes));
84
85
pa.push([], [{}]);
86
expect(events).toHaveLength(2);
87
expect(events[0]).toEqual({
88
type: 'insert',
89
path: [0],
90
value: [],
91
});
92
expect(events[1]).toEqual({
93
type: 'insert',
94
path: [1],
95
value: [{}],
96
});
97
pa[0].push('name');
98
expect(events).toHaveLength(3);
99
100
expect(events[2]).toEqual({
101
type: 'insert',
102
path: [0, 0],
103
value: 'name',
104
});
105
pa[1][0].prop = 'more';
106
expect(events).toHaveLength(4);
107
expect(events[3]).toEqual({
108
type: 'insert',
109
path: [1, 0, 'prop'],
110
value: 'more',
111
});
112
});
113
114
test('array pop - primitives', () => {
115
const events = [];
116
const pa = ObjectObserver.create(['some'], changes => events.push(...changes));
117
118
const popped = pa.pop();
119
120
expect(events).toHaveLength(1);
121
expect(events[0]).toEqual({
122
type: 'delete',
123
path: [0],
124
});
125
expect(popped).toBe('some');
126
});
127
128
test('array pop - objects', () => {
129
const events = [];
130
const pa = ObjectObserver.create([{ test: 'text' }], changes => events.push(...changes));
131
const pad: any = pa[0];
132
133
pa[0].test = 'test';
134
pad.test = 'more';
135
expect(events).toHaveLength(2);
136
137
const popped: any = pa.pop();
138
expect(popped.test).toBe('more');
139
expect(events).toHaveLength(3);
140
141
popped.new = 'value';
142
expect(events).toHaveLength(3);
143
144
const eventsA = [];
145
const newPad = ObjectObserver.create(pad, changes => eventsA.push(...changes));
146
newPad.test = 'change';
147
expect(eventsA).toHaveLength(1);
148
});
149
150
test('array unshift - primitives', () => {
151
const events = [];
152
let callbacks = 0;
153
const pa = ObjectObserver.create([], changes => {
154
events.push(...changes);
155
callbacks += 1;
156
});
157
158
pa.unshift('a');
159
pa.unshift('b', 'c');
160
expect(events).toHaveLength(3);
161
expect(callbacks).toBe(2);
162
expect(events[0]).toEqual({
163
type: 'insert',
164
path: [0],
165
value: 'a',
166
});
167
expect(events[1]).toEqual({
168
type: 'insert',
169
path: [0],
170
value: 'b',
171
});
172
expect(events[2]).toEqual({
173
type: 'insert',
174
path: [1],
175
value: 'c',
176
});
177
});
178
179
test('array unshift - objects', () => {
180
const events = [];
181
const pa = ObjectObserver.create([{ text: 'original' }], changes => {
182
events.push(...changes);
183
});
184
185
pa.unshift({ text: 'initial' });
186
expect(events).toHaveLength(1);
187
expect(events[0]).toEqual({
188
type: 'insert',
189
path: [0],
190
value: { text: 'initial' },
191
});
192
events.splice(0);
193
194
pa[0].text = 'name';
195
pa[1].text = 'other';
196
expect(events).toHaveLength(2);
197
expect(events[0]).toEqual({
198
type: 'update',
199
path: [0, 'text'],
200
value: 'name',
201
});
202
expect(events[1]).toEqual({
203
type: 'update',
204
path: [1, 'text'],
205
value: 'other',
206
});
207
});
208
209
test('array unshift - arrays', () => {
210
const events = [];
211
const pa = ObjectObserver.create([{ text: 'original' }], changes => {
212
events.push(...changes);
213
});
214
215
pa.unshift([{}] as any);
216
expect(events).toHaveLength(1);
217
expect(events[0]).toEqual({
218
type: 'insert',
219
path: [0],
220
value: [{}],
221
});
222
events.splice(0);
223
224
pa[0][0].text = 'name';
225
pa[1].text = 'other';
226
expect(events).toHaveLength(2);
227
expect(events[0]).toEqual({
228
type: 'insert',
229
path: [0, 0, 'text'],
230
value: 'name',
231
});
232
expect(events[1]).toEqual({
233
type: 'update',
234
path: [1, 'text'],
235
value: 'other',
236
});
237
});
238
239
test('array shift - primitives', () => {
240
const events = [];
241
const pa = ObjectObserver.create(['some'], changes => {
242
events.push(...changes);
243
});
244
245
const shifted = pa.shift();
246
expect(events).toHaveLength(1);
247
expect(events[0]).toEqual({
248
type: 'delete',
249
path: [0],
250
});
251
expect(shifted).toBe('some');
252
});
253
254
test('array shift - objects', () => {
255
const events = [];
256
const pa = ObjectObserver.create(
257
[{ text: 'a', inner: { test: 'more' } }, { text: 'b' }],
258
changes => events.push(...changes),
259
);
260
const pa0 = pa[0];
261
const pa0i: any = pa0.inner;
262
263
pa[0].text = 'b';
264
pa0i.test = 'test';
265
expect(events).toHaveLength(2);
266
events.splice(0);
267
268
const shifted = pa.shift();
269
expect(shifted.text).toBe('b');
270
expect(shifted.inner.test).toBe('test');
271
272
expect(events).toHaveLength(1);
273
expect(events[0]).toEqual({
274
type: 'delete',
275
path: [0],
276
});
277
events.splice(0);
278
279
pa[0].text = 'c';
280
expect(events).toHaveLength(1);
281
expect(events[0]).toEqual({
282
type: 'update',
283
path: [0, 'text'],
284
value: 'c',
285
});
286
events.splice(0);
287
288
shifted.text = 'd';
289
expect(events).toHaveLength(0);
290
});
291
292
test('array reverse - primitives (flat array)', () => {
293
const events = [];
294
const pa = ObjectObserver.create([1, 2, 3], changes => events.push(...changes));
295
296
const reversed = pa.reverse();
297
298
expect(reversed).toEqual(pa);
299
expect(events).toHaveLength(1);
300
expect(events[0]).toEqual({
301
type: 'reorder',
302
path: [],
303
value: [2, 1, 0],
304
});
305
});
306
307
test('array reverse - primitives (nested array)', () => {
308
const events = [];
309
const pa = ObjectObserver.create({ a1: { a2: [1, 2, 3] } }, changes => events.push(...changes));
310
311
const reversed = pa.a1.a2.reverse();
312
313
expect(reversed).toEqual(pa.a1.a2);
314
expect(events).toHaveLength(1);
315
expect(events[0]).toEqual({
316
type: 'reorder',
317
path: ['a1', 'a2'],
318
value: [2, 1, 0],
319
});
320
});
321
322
test('array reverse - objects', () => {
323
const events = [];
324
const pa = ObjectObserver.create([{ name: 'a' }, { name: 'b' }, { name: 'c' }], changes =>
325
events.push(...changes),
326
);
327
328
pa[0].name = 'A';
329
const reversed = pa.reverse();
330
pa[0].name = 'C';
331
332
expect(reversed).toEqual(pa);
333
expect(events).toHaveLength(3);
334
expect(events[0]).toEqual({
335
type: 'update',
336
path: [0, 'name'],
337
value: 'A',
338
});
339
expect(events[1]).toEqual({
340
type: 'reorder',
341
path: [],
342
value: [2, 1, 0],
343
});
344
expect(events[2]).toEqual({
345
type: 'update',
346
path: [0, 'name'],
347
value: 'C',
348
});
349
});
350
351
test('array sort - primitives (flat array)', () => {
352
const events = [];
353
const pa = ObjectObserver.create([3, 2, 1], changes => events.push(...changes));
354
355
let sorted = pa.sort();
356
357
expect(sorted).toEqual(pa);
358
expect(events).toHaveLength(1);
359
expect(events[0]).toEqual({
360
type: 'reorder',
361
path: [],
362
value: [2, 1, 0],
363
});
364
expect(pa).toEqual([1, 2, 3]);
365
366
sorted = pa.sort((a, b) => {
367
return a < b ? 1 : -1;
368
});
369
expect(sorted).toEqual(pa);
370
371
expect(events).toHaveLength(2);
372
expect(events[0]).toEqual({
373
type: 'reorder',
374
path: [],
375
value: [2, 1, 0],
376
});
377
expect(pa).toEqual([3, 2, 1]);
378
});
379
380
test('array sort - primitives (flat array with duplicates)', () => {
381
const events = [];
382
const pa = ObjectObserver.create([3, 2, 1, 2, 1], changes => events.push(...changes));
383
384
const sorted = pa.sort();
385
386
expect(sorted).toEqual(pa);
387
expect(events).toHaveLength(1);
388
expect(events[0]).toEqual({
389
type: 'reorder',
390
path: [],
391
value: [2, 4, 1, 3, 0],
392
});
393
expect(pa).toEqual([1, 1, 2, 2, 3]);
394
});
395
396
test('array sort - objects', () => {
397
const events = [];
398
const pa = ObjectObserver.create([{ name: 'a' }, { name: 'b' }, { name: 'c' }], changes =>
399
events.push(...changes),
400
);
401
402
pa[0].name = 'A';
403
const sorted = pa.sort((a, b) => {
404
return a.name < b.name ? 1 : -1;
405
});
406
pa[0].name = 'C';
407
408
if (sorted !== pa) throw new Error('sort base functionality broken');
409
410
expect(events).toHaveLength(3);
411
expect(events[0]).toEqual({
412
type: 'update',
413
path: [0, 'name'],
414
value: 'A',
415
});
416
expect(events[1]).toEqual({
417
type: 'reorder',
418
path: [],
419
value: [2, 1, 0],
420
});
421
expect(events[2]).toEqual({
422
type: 'update',
423
path: [0, 'name'],
424
value: 'C',
425
});
426
});
427
428
test('array fill - primitives', () => {
429
const events = [];
430
const pa: any[] = ObjectObserver.create([1, 2, 3], changes => events.push(...changes));
431
432
const filled = pa.fill('a');
433
if (filled !== pa) throw new Error('fill base functionality broken');
434
435
expect(events).toHaveLength(3);
436
expect(events[0]).toEqual({
437
type: 'update',
438
path: [0],
439
value: 'a',
440
});
441
expect(events[1]).toEqual({
442
type: 'update',
443
path: [1],
444
value: 'a',
445
});
446
expect(events[2]).toEqual({
447
type: 'update',
448
path: [2],
449
value: 'a',
450
});
451
events.splice(0);
452
453
pa.fill('b', 1, 3);
454
455
expect(events).toHaveLength(2);
456
expect(events[0]).toEqual({
457
type: 'update',
458
path: [1],
459
value: 'b',
460
});
461
expect(events[1]).toEqual({
462
type: 'update',
463
path: [2],
464
value: 'b',
465
});
466
events.splice(0);
467
468
pa.fill('c', -1, 3);
469
470
expect(events).toHaveLength(1);
471
expect(events[0]).toEqual({
472
type: 'update',
473
path: [2],
474
value: 'c',
475
});
476
events.splice(0);
477
478
// simulating insertion of a new item into array (fill does not extend an array, so we may do it only on internal items)
479
delete pa[1];
480
pa.fill('d', 1, 2);
481
482
expect(events).toHaveLength(2);
483
expect(events[0]).toEqual({
484
type: 'delete',
485
path: [1],
486
});
487
expect(events[1]).toEqual({
488
type: 'update',
489
path: [1],
490
value: 'd',
491
});
492
});
493
494
test('array fill - objects', () => {
495
const events = [];
496
const pa: any = ObjectObserver.create(
497
[{ some: 'text' }, { some: 'else' }, { some: 'more' }],
498
changes => events.push(...changes),
499
);
500
501
const filled = pa.fill({ name: 'Niv' });
502
if (filled !== pa) throw new Error('fill base functionality broken');
503
504
expect(events).toHaveLength(3);
505
expect(events[0]).toEqual({
506
type: 'update',
507
path: [0],
508
value: { name: 'Niv' },
509
});
510
expect(events[1]).toEqual({
511
type: 'update',
512
path: [1],
513
value: { name: 'Niv' },
514
});
515
expect(events[2]).toEqual({
516
type: 'update',
517
path: [2],
518
value: { name: 'Niv' },
519
});
520
events.length = 0;
521
522
pa[1].name = 'David';
523
expect(events[0]).toEqual({
524
type: 'update',
525
path: [1, 'name'],
526
value: 'David',
527
});
528
expect(events).toHaveLength(1);
529
});
530
531
test('array fill - arrays', () => {
532
const events = [];
533
const pa: any = ObjectObserver.create(
534
[{ some: 'text' }, { some: 'else' }, { some: 'more' }],
535
changes => events.push(...changes),
536
);
537
538
const filled = pa.fill([{ name: 'Niv' }]);
539
expect(filled).toEqual(pa);
540
541
expect(events).toHaveLength(3);
542
expect(events[0]).toEqual({
543
type: 'update',
544
path: [0],
545
value: [{ name: 'Niv' }],
546
});
547
expect(events[1]).toEqual({
548
type: 'update',
549
path: [1],
550
value: [{ name: 'Niv' }],
551
});
552
expect(events[2]).toEqual({
553
type: 'update',
554
path: [2],
555
value: [{ name: 'Niv' }],
556
});
557
events.length = 0;
558
559
pa[1][0].name = 'David';
560
561
expect(events).toHaveLength(1);
562
expect(events[0]).toEqual({
563
type: 'update',
564
path: [1, 0, 'name'],
565
value: 'David',
566
});
567
});
568
569
test('array splice - primitives', () => {
570
const events = [];
571
let callbacks = 0;
572
const pa: any = ObjectObserver.create([1, 2, 3, 4, 5, 6], changes => {
573
events.push(...changes);
574
callbacks += 1;
575
});
576
577
const spliced = pa.splice(2, 2, 'a');
578
expect(spliced).toEqual([3, 4]);
579
580
expect(events).toHaveLength(2);
581
expect(callbacks).toBe(1);
582
expect(events[0]).toEqual({
583
type: 'update',
584
path: [2],
585
value: 'a',
586
});
587
expect(events[1]).toEqual({
588
type: 'delete',
589
path: [3],
590
});
591
events.splice(0);
592
callbacks = 0;
593
594
// pa = [1,2,'a',5,6]
595
pa.splice(-3);
596
597
expect(events).toHaveLength(3);
598
expect(callbacks).toBe(1);
599
expect(events[0]).toEqual({
600
type: 'delete',
601
path: [2],
602
});
603
expect(events[1]).toEqual({
604
type: 'delete',
605
path: [3],
606
});
607
expect(events[2]).toEqual({
608
type: 'delete',
609
path: [4],
610
});
611
expect(pa).toHaveLength(2);
612
events.length = 0;
613
callbacks = 0;
614
615
// pa = [1,2]
616
pa.splice(0);
617
618
expect(events).toHaveLength(2);
619
expect(callbacks).toBe(1);
620
expect(events[0]).toEqual({
621
type: 'delete',
622
path: [0],
623
});
624
expect(events[1]).toEqual({
625
type: 'delete',
626
path: [1],
627
});
628
});
629
630
test('array splice - objects', () => {
631
const events = [];
632
const pa = ObjectObserver.create(
633
[{ text: 'a' }, { text: 'b' }, { text: 'c' }, { text: 'd' }],
634
changes => events.push(...changes),
635
);
636
637
pa.splice(1, 2, { text: '1' });
638
639
expect(events).toHaveLength(2);
640
expect(events[0]).toEqual({
641
type: 'update',
642
path: [1],
643
value: { text: '1' },
644
});
645
expect(events[1]).toEqual({
646
type: 'delete',
647
path: [2],
648
});
649
expect(pa).toHaveLength(3);
650
events.splice(0);
651
652
pa[1].text = 'B';
653
pa[2].text = 'D';
654
655
expect(events).toHaveLength(2);
656
expect(events[0]).toEqual({
657
type: 'update',
658
path: [1, 'text'],
659
value: 'B',
660
});
661
expect(events[1]).toEqual({
662
type: 'update',
663
path: [2, 'text'],
664
value: 'D',
665
});
666
events.splice(0);
667
668
pa.splice(1, 1, { text: 'A' }, { text: 'B' });
669
670
expect(events).toHaveLength(2);
671
expect(events[0]).toEqual({
672
type: 'update',
673
path: [1],
674
value: { text: 'A' },
675
});
676
expect(events[1]).toEqual({
677
type: 'insert',
678
path: [2],
679
value: { text: 'B' },
680
});
681
events.splice(0);
682
683
pa[3].text = 'C';
684
685
expect(events).toHaveLength(1);
686
expect(events[0]).toEqual({
687
type: 'update',
688
path: [3, 'text'],
689
value: 'C',
690
});
691
});
692
693
describe('copyWithin', () => {
694
test('array copyWithin - primitives', () => {
695
const events = [];
696
let callbacks = 0;
697
const pa = ObjectObserver.create([1, 2, 3, 4, 5, 6], changes => {
698
events.push(...changes);
699
callbacks += 1;
700
});
701
702
let copied = pa.copyWithin(2, 0, 3);
703
expect(pa).toEqual(copied);
704
expect(events).toHaveLength(3);
705
expect(callbacks).toBe(1);
706
expect(events[0]).toEqual({
707
type: 'update',
708
path: [2],
709
value: 1,
710
});
711
expect(events[1]).toEqual({
712
type: 'update',
713
path: [3],
714
value: 2,
715
});
716
expect(events[2]).toEqual({
717
type: 'update',
718
path: [4],
719
value: 3,
720
});
721
events.splice(0);
722
callbacks = 0;
723
724
// pa = [1,2,1,2,3,6]
725
copied = pa.copyWithin(-3, 0);
726
expect(pa).toEqual(copied);
727
expect(events).toHaveLength(3);
728
expect(callbacks).toBe(1);
729
expect(events[0]).toEqual({
730
type: 'update',
731
path: [3],
732
value: 1,
733
});
734
expect(events[1]).toEqual({
735
type: 'update',
736
path: [4],
737
value: 2,
738
});
739
expect(events[2]).toEqual({
740
type: 'update',
741
path: [5],
742
value: 1,
743
});
744
events.splice(0);
745
callbacks = 0;
746
747
// pa = [1,2,1,1,2,1]
748
copied = pa.copyWithin(1, -3, 9);
749
expect(pa).toEqual(copied);
750
expect(events).toHaveLength(2);
751
expect(callbacks).toBe(1);
752
expect(events[0]).toEqual({
753
type: 'update',
754
path: [1],
755
value: 1,
756
});
757
expect(events[1]).toEqual({
758
type: 'update',
759
path: [2],
760
value: 2,
761
});
762
763
// update at index 4 should not be evented, since 1 === 1
764
events.splice(0);
765
callbacks = 0;
766
});
767
768
test('array copyWithin - objects', () => {
769
const events = [];
770
const pa = ObjectObserver.create(
771
[{ text: 'a' }, { text: 'b' }, { text: 'c' }, { text: 'd' }],
772
changes => {
773
events.push(...changes);
774
},
775
);
776
777
const copied = pa.copyWithin(1, 2, 3);
778
expect(pa).toEqual(copied);
779
expect(events).toHaveLength(1);
780
expect(events[0]).toEqual({
781
type: 'update',
782
path: [1],
783
value: { text: 'c' },
784
});
785
events.length = 0;
786
787
pa[1].text = 'B';
788
pa[2].text = 'D';
789
expect(events).toHaveLength(2);
790
expect(events[0]).toEqual({
791
type: 'update',
792
path: [1, 'text'],
793
value: 'B',
794
});
795
expect(events[1]).toEqual({
796
type: 'update',
797
path: [2, 'text'],
798
value: 'D',
799
});
800
});
801
});
802
803