Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/common/event.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import assert from 'assert';
6
import { stub } from 'sinon';
7
import { timeout } from '../../common/async.js';
8
import { CancellationToken } from '../../common/cancellation.js';
9
import { errorHandler, setUnexpectedErrorHandler } from '../../common/errors.js';
10
import { AsyncEmitter, DebounceEmitter, DynamicListEventMultiplexer, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, ListenerLeakError, ListenerRefusalError, MicrotaskEmitter, PauseableEmitter, Relay, createEventDeliveryQueue } from '../../common/event.js';
11
import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, DisposableTracker } from '../../common/lifecycle.js';
12
import { observableValue, transaction } from '../../common/observable.js';
13
import { MicrotaskDelay } from '../../common/symbols.js';
14
import { runWithFakedTimers } from './timeTravelScheduler.js';
15
import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';
16
import { tail } from '../../common/arrays.js';
17
18
namespace Samples {
19
20
export class EventCounter {
21
22
count = 0;
23
24
reset() {
25
this.count = 0;
26
}
27
28
onEvent() {
29
this.count += 1;
30
}
31
}
32
33
export class Document3 {
34
35
private readonly _onDidChange = new Emitter<string>();
36
37
onDidChange: Event<string> = this._onDidChange.event;
38
39
setText(value: string) {
40
//...
41
this._onDidChange.fire(value);
42
}
43
44
dispose() {
45
this._onDidChange.dispose();
46
}
47
48
}
49
}
50
51
suite('Event utils dispose', function () {
52
53
const ds = ensureNoDisposablesAreLeakedInTestSuite();
54
55
let tracker = new DisposableTracker();
56
57
function assertDisposablesCount(expected: number | Array<IDisposable>) {
58
if (Array.isArray(expected)) {
59
const instances = new Set(expected);
60
const actualInstances = tracker.getTrackedDisposables();
61
assert.strictEqual(actualInstances.length, expected.length);
62
63
for (const item of actualInstances) {
64
assert.ok(instances.has(item));
65
}
66
67
} else {
68
assert.strictEqual(tracker.getTrackedDisposables().length, expected);
69
}
70
71
}
72
73
setup(() => {
74
tracker = new DisposableTracker();
75
setDisposableTracker(tracker);
76
});
77
78
teardown(function () {
79
setDisposableTracker(null);
80
});
81
82
test('no leak with snapshot-utils', function () {
83
84
const store = new DisposableStore();
85
const emitter = ds.add(new Emitter<number>());
86
const evens = Event.filter(emitter.event, n => n % 2 === 0, store);
87
assertDisposablesCount(1); // snaphot only listen when `evens` is being listened on
88
89
let all = 0;
90
const leaked = evens(n => all += n);
91
assert.ok(isDisposable(leaked));
92
assertDisposablesCount(3);
93
94
emitter.dispose();
95
store.dispose();
96
assertDisposablesCount([leaked]); // leaked is still there
97
});
98
99
test('no leak with debounce-util', function () {
100
const store = new DisposableStore();
101
const emitter = ds.add(new Emitter<number>());
102
const debounced = Event.debounce(emitter.event, (l) => 0, undefined, undefined, undefined, undefined, store);
103
assertDisposablesCount(1); // debounce only listens when `debounce` is being listened on
104
105
let all = 0;
106
const leaked = debounced(n => all += n);
107
assert.ok(isDisposable(leaked));
108
assertDisposablesCount(3);
109
110
emitter.dispose();
111
store.dispose();
112
113
assertDisposablesCount([leaked]); // leaked is still there
114
});
115
});
116
117
suite('Event', function () {
118
119
const ds = ensureNoDisposablesAreLeakedInTestSuite();
120
121
const counter = new Samples.EventCounter();
122
123
setup(() => counter.reset());
124
125
test('Emitter plain', function () {
126
127
const doc = ds.add(new Samples.Document3());
128
129
const subscription = doc.onDidChange(counter.onEvent, counter);
130
131
doc.setText('far');
132
doc.setText('boo');
133
134
// unhook listener
135
subscription.dispose();
136
doc.setText('boo');
137
assert.strictEqual(counter.count, 2);
138
});
139
140
test('Emitter duplicate functions', () => {
141
const calls: string[] = [];
142
const a = (v: string) => calls.push(`a${v}`);
143
const b = (v: string) => calls.push(`b${v}`);
144
145
const emitter = ds.add(new Emitter<string>());
146
147
ds.add(emitter.event(a));
148
ds.add(emitter.event(b));
149
const s2 = emitter.event(a);
150
151
emitter.fire('1');
152
assert.deepStrictEqual(calls, ['a1', 'b1', 'a1']);
153
154
s2.dispose();
155
calls.length = 0;
156
emitter.fire('2');
157
assert.deepStrictEqual(calls, ['a2', 'b2']);
158
});
159
160
test('Emitter, dispose listener during emission', () => {
161
for (let keepFirstMod = 1; keepFirstMod < 4; keepFirstMod++) {
162
const emitter = ds.add(new Emitter<void>());
163
const calls: number[] = [];
164
const disposables = Array.from({ length: 25 }, (_, n) => ds.add(emitter.event(() => {
165
if (n % keepFirstMod === 0) {
166
disposables[n].dispose();
167
}
168
calls.push(n);
169
})));
170
171
emitter.fire();
172
assert.deepStrictEqual(calls, Array.from({ length: 25 }, (_, n) => n));
173
}
174
});
175
176
test('Emitter, dispose emitter during emission', () => {
177
const emitter = ds.add(new Emitter<void>());
178
const calls: number[] = [];
179
const disposables = Array.from({ length: 25 }, (_, n) => ds.add(emitter.event(() => {
180
if (n === 10) {
181
emitter.dispose();
182
}
183
calls.push(n);
184
})));
185
186
emitter.fire();
187
disposables.forEach(d => d.dispose());
188
assert.deepStrictEqual(calls, Array.from({ length: 11 }, (_, n) => n));
189
});
190
191
test('Emitter, shared delivery queue', () => {
192
const deliveryQueue = createEventDeliveryQueue();
193
const emitter1 = ds.add(new Emitter<number>({ deliveryQueue }));
194
const emitter2 = ds.add(new Emitter<number>({ deliveryQueue }));
195
196
const calls: string[] = [];
197
ds.add(emitter1.event(d => { calls.push(`${d}a`); if (d === 1) { emitter2.fire(2); } }));
198
ds.add(emitter1.event(d => { calls.push(`${d}b`); }));
199
200
ds.add(emitter2.event(d => { calls.push(`${d}c`); emitter1.dispose(); }));
201
ds.add(emitter2.event(d => { calls.push(`${d}d`); }));
202
203
emitter1.fire(1);
204
205
// 1. Check that 2 is not delivered before 1 finishes
206
// 2. Check that 2 finishes getting delivered even if one emitter is disposed
207
assert.deepStrictEqual(calls, ['1a', '1b', '2c', '2d']);
208
});
209
210
test('Emitter, handles removal during 3', () => {
211
const fn1 = stub();
212
const fn2 = stub();
213
const emitter = ds.add(new Emitter<string>());
214
215
ds.add(emitter.event(fn1));
216
const h = emitter.event(() => {
217
h.dispose();
218
});
219
ds.add(emitter.event(fn2));
220
emitter.fire('foo');
221
222
assert.deepStrictEqual(fn2.args, [['foo']]);
223
assert.deepStrictEqual(fn1.args, [['foo']]);
224
});
225
226
test('Emitter, handles removal during 2', () => {
227
const fn1 = stub();
228
const emitter = ds.add(new Emitter<string>());
229
230
ds.add(emitter.event(fn1));
231
const h = emitter.event(() => {
232
h.dispose();
233
});
234
emitter.fire('foo');
235
236
assert.deepStrictEqual(fn1.args, [['foo']]);
237
});
238
239
test('Emitter, bucket', function () {
240
241
const bucket: IDisposable[] = [];
242
const doc = ds.add(new Samples.Document3());
243
const subscription = doc.onDidChange(counter.onEvent, counter, bucket);
244
245
doc.setText('far');
246
doc.setText('boo');
247
248
// unhook listener
249
while (bucket.length) {
250
bucket.pop()!.dispose();
251
}
252
doc.setText('boo');
253
254
// noop
255
subscription.dispose();
256
257
doc.setText('boo');
258
assert.strictEqual(counter.count, 2);
259
});
260
261
test('Emitter, store', function () {
262
263
const bucket = ds.add(new DisposableStore());
264
const doc = ds.add(new Samples.Document3());
265
const subscription = doc.onDidChange(counter.onEvent, counter, bucket);
266
267
doc.setText('far');
268
doc.setText('boo');
269
270
// unhook listener
271
bucket.clear();
272
doc.setText('boo');
273
274
// noop
275
subscription.dispose();
276
277
doc.setText('boo');
278
assert.strictEqual(counter.count, 2);
279
});
280
281
test('onFirstAdd|onLastRemove', () => {
282
283
let firstCount = 0;
284
let lastCount = 0;
285
const a = ds.add(new Emitter({
286
onWillAddFirstListener() { firstCount += 1; },
287
onDidRemoveLastListener() { lastCount += 1; }
288
}));
289
290
assert.strictEqual(firstCount, 0);
291
assert.strictEqual(lastCount, 0);
292
293
let subscription1 = ds.add(a.event(function () { }));
294
const subscription2 = ds.add(a.event(function () { }));
295
assert.strictEqual(firstCount, 1);
296
assert.strictEqual(lastCount, 0);
297
298
subscription1.dispose();
299
assert.strictEqual(firstCount, 1);
300
assert.strictEqual(lastCount, 0);
301
302
subscription2.dispose();
303
assert.strictEqual(firstCount, 1);
304
assert.strictEqual(lastCount, 1);
305
306
subscription1 = ds.add(a.event(function () { }));
307
assert.strictEqual(firstCount, 2);
308
assert.strictEqual(lastCount, 1);
309
});
310
311
test('onDidAddListener', () => {
312
let count = 0;
313
const a = ds.add(new Emitter({
314
onDidAddListener() { count += 1; }
315
}));
316
317
assert.strictEqual(count, 0);
318
319
let subscription = ds.add(a.event(function () { }));
320
assert.strictEqual(count, 1);
321
322
subscription.dispose();
323
assert.strictEqual(count, 1);
324
325
subscription = ds.add(a.event(function () { }));
326
assert.strictEqual(count, 2);
327
328
subscription.dispose();
329
assert.strictEqual(count, 2);
330
});
331
332
test('onWillRemoveListener', () => {
333
let count = 0;
334
const a = ds.add(new Emitter({
335
onWillRemoveListener() { count += 1; }
336
}));
337
338
assert.strictEqual(count, 0);
339
340
let subscription = ds.add(a.event(function () { }));
341
assert.strictEqual(count, 0);
342
343
subscription.dispose();
344
assert.strictEqual(count, 1);
345
346
subscription = ds.add(a.event(function () { }));
347
assert.strictEqual(count, 1);
348
});
349
350
test('throwingListener', () => {
351
const origErrorHandler = errorHandler.getUnexpectedErrorHandler();
352
setUnexpectedErrorHandler(() => null);
353
354
try {
355
const a = ds.add(new Emitter<undefined>());
356
let hit = false;
357
ds.add(a.event(function () {
358
// eslint-disable-next-line no-throw-literal
359
throw 9;
360
}));
361
ds.add(a.event(function () {
362
hit = true;
363
}));
364
a.fire(undefined);
365
assert.strictEqual(hit, true);
366
367
} finally {
368
setUnexpectedErrorHandler(origErrorHandler);
369
}
370
});
371
372
test('throwingListener (custom handler)', () => {
373
374
const allError: any[] = [];
375
376
const a = ds.add(new Emitter<undefined>({
377
onListenerError(e) { allError.push(e); }
378
}));
379
let hit = false;
380
ds.add(a.event(function () {
381
// eslint-disable-next-line no-throw-literal
382
throw 9;
383
}));
384
ds.add(a.event(function () {
385
hit = true;
386
}));
387
a.fire(undefined);
388
assert.strictEqual(hit, true);
389
assert.deepStrictEqual(allError, [9]);
390
391
});
392
393
test('throw ListenerLeakError', () => {
394
395
const store = new DisposableStore();
396
const allError: any[] = [];
397
398
const a = ds.add(new Emitter<undefined>({
399
onListenerError(e) { allError.push(e); },
400
leakWarningThreshold: 3,
401
}));
402
403
for (let i = 0; i < 11; i++) {
404
a.event(() => { }, undefined, store);
405
}
406
407
assert.deepStrictEqual(allError.length, 5);
408
const [start, rest] = tail(allError);
409
assert.ok(rest instanceof ListenerRefusalError);
410
411
for (const item of start) {
412
assert.ok(item instanceof ListenerLeakError);
413
}
414
415
store.dispose();
416
});
417
418
test('reusing event function and context', function () {
419
let counter = 0;
420
function listener() {
421
counter += 1;
422
}
423
const context = {};
424
425
const emitter = ds.add(new Emitter<undefined>());
426
const reg1 = emitter.event(listener, context);
427
const reg2 = emitter.event(listener, context);
428
429
emitter.fire(undefined);
430
assert.strictEqual(counter, 2);
431
432
reg1.dispose();
433
emitter.fire(undefined);
434
assert.strictEqual(counter, 3);
435
436
reg2.dispose();
437
emitter.fire(undefined);
438
assert.strictEqual(counter, 3);
439
});
440
441
test('DebounceEmitter', async function () {
442
return runWithFakedTimers({}, async function () {
443
444
let callCount = 0;
445
let sum = 0;
446
const emitter = new DebounceEmitter<number>({
447
merge: arr => {
448
callCount += 1;
449
return arr.reduce((p, c) => p + c);
450
}
451
});
452
453
ds.add(emitter.event(e => { sum = e; }));
454
455
const p = Event.toPromise(emitter.event);
456
457
emitter.fire(1);
458
emitter.fire(2);
459
460
await p;
461
462
assert.strictEqual(callCount, 1);
463
assert.strictEqual(sum, 3);
464
});
465
});
466
467
test('Microtask Emitter', (done) => {
468
let count = 0;
469
assert.strictEqual(count, 0);
470
const emitter = new MicrotaskEmitter<void>();
471
const listener = emitter.event(() => {
472
count++;
473
});
474
emitter.fire();
475
assert.strictEqual(count, 0);
476
emitter.fire();
477
assert.strictEqual(count, 0);
478
// Should wait until the event loop ends and therefore be the last thing called
479
setTimeout(() => {
480
assert.strictEqual(count, 3);
481
done();
482
}, 0);
483
queueMicrotask(() => {
484
assert.strictEqual(count, 2);
485
count++;
486
listener.dispose();
487
});
488
});
489
490
test('Emitter - In Order Delivery', function () {
491
const a = ds.add(new Emitter<string>());
492
const listener2Events: string[] = [];
493
ds.add(a.event(function listener1(event) {
494
if (event === 'e1') {
495
a.fire('e2');
496
// assert that all events are delivered at this point
497
assert.deepStrictEqual(listener2Events, ['e1', 'e2']);
498
}
499
}));
500
ds.add(a.event(function listener2(event) {
501
listener2Events.push(event);
502
}));
503
a.fire('e1');
504
505
// assert that all events are delivered in order
506
assert.deepStrictEqual(listener2Events, ['e1', 'e2']);
507
});
508
509
test('Emitter, - In Order Delivery 3x', function () {
510
const a = ds.add(new Emitter<string>());
511
const listener2Events: string[] = [];
512
ds.add(a.event(function listener1(event) {
513
if (event === 'e2') {
514
a.fire('e3');
515
// assert that all events are delivered at this point
516
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
517
}
518
}));
519
ds.add(a.event(function listener1(event) {
520
if (event === 'e1') {
521
a.fire('e2');
522
// assert that all events are delivered at this point
523
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
524
}
525
}));
526
ds.add(a.event(function listener2(event) {
527
listener2Events.push(event);
528
}));
529
a.fire('e1');
530
531
// assert that all events are delivered in order
532
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
533
});
534
535
test('Cannot read property \'_actual\' of undefined #142204', function () {
536
const e = ds.add(new Emitter<number>());
537
const dispo = e.event(() => { });
538
dispo.dispose.call(undefined); // assert that disposable can be called with this
539
});
540
});
541
542
suite('AsyncEmitter', function () {
543
544
const ds = ensureNoDisposablesAreLeakedInTestSuite();
545
546
test('event has waitUntil-function', async function () {
547
548
interface E extends IWaitUntil {
549
foo: boolean;
550
bar: number;
551
}
552
553
const emitter = new AsyncEmitter<E>();
554
555
ds.add(emitter.event(e => {
556
assert.strictEqual(e.foo, true);
557
assert.strictEqual(e.bar, 1);
558
assert.strictEqual(typeof e.waitUntil, 'function');
559
}));
560
561
emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None);
562
emitter.dispose();
563
});
564
565
test('sequential delivery', async function () {
566
return runWithFakedTimers({}, async function () {
567
568
interface E extends IWaitUntil {
569
foo: boolean;
570
}
571
572
let globalState = 0;
573
const emitter = new AsyncEmitter<E>();
574
575
ds.add(emitter.event(e => {
576
e.waitUntil(timeout(10).then(_ => {
577
assert.strictEqual(globalState, 0);
578
globalState += 1;
579
}));
580
}));
581
582
ds.add(emitter.event(e => {
583
e.waitUntil(timeout(1).then(_ => {
584
assert.strictEqual(globalState, 1);
585
globalState += 1;
586
}));
587
}));
588
589
await emitter.fireAsync({ foo: true }, CancellationToken.None);
590
assert.strictEqual(globalState, 2);
591
});
592
});
593
594
test('sequential, in-order delivery', async function () {
595
return runWithFakedTimers({}, async function () {
596
597
interface E extends IWaitUntil {
598
foo: number;
599
}
600
const events: number[] = [];
601
let done = false;
602
const emitter = new AsyncEmitter<E>();
603
604
// e1
605
ds.add(emitter.event(e => {
606
e.waitUntil(timeout(10).then(async _ => {
607
if (e.foo === 1) {
608
await emitter.fireAsync({ foo: 2 }, CancellationToken.None);
609
assert.deepStrictEqual(events, [1, 2]);
610
done = true;
611
}
612
}));
613
}));
614
615
// e2
616
ds.add(emitter.event(e => {
617
events.push(e.foo);
618
e.waitUntil(timeout(7));
619
}));
620
621
await emitter.fireAsync({ foo: 1 }, CancellationToken.None);
622
assert.ok(done);
623
});
624
});
625
626
test('catch errors', async function () {
627
const origErrorHandler = errorHandler.getUnexpectedErrorHandler();
628
setUnexpectedErrorHandler(() => null);
629
630
interface E extends IWaitUntil {
631
foo: boolean;
632
}
633
634
let globalState = 0;
635
const emitter = new AsyncEmitter<E>();
636
637
ds.add(emitter.event(e => {
638
globalState += 1;
639
e.waitUntil(new Promise((_r, reject) => reject(new Error())));
640
}));
641
642
ds.add(emitter.event(e => {
643
globalState += 1;
644
e.waitUntil(timeout(10));
645
e.waitUntil(timeout(20).then(() => globalState++)); // multiple `waitUntil` are supported and awaited on
646
}));
647
648
await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => {
649
assert.strictEqual(globalState, 3);
650
}).catch(e => {
651
console.log(e);
652
assert.ok(false);
653
});
654
655
setUnexpectedErrorHandler(origErrorHandler);
656
});
657
});
658
659
suite('PausableEmitter', function () {
660
661
const ds = ensureNoDisposablesAreLeakedInTestSuite();
662
663
test('basic', function () {
664
const data: number[] = [];
665
const emitter = ds.add(new PauseableEmitter<number>());
666
667
ds.add(emitter.event(e => data.push(e)));
668
emitter.fire(1);
669
emitter.fire(2);
670
671
assert.deepStrictEqual(data, [1, 2]);
672
});
673
674
test('pause/resume - no merge', function () {
675
const data: number[] = [];
676
const emitter = ds.add(new PauseableEmitter<number>());
677
678
ds.add(emitter.event(e => data.push(e)));
679
emitter.fire(1);
680
emitter.fire(2);
681
assert.deepStrictEqual(data, [1, 2]);
682
683
emitter.pause();
684
emitter.fire(3);
685
emitter.fire(4);
686
assert.deepStrictEqual(data, [1, 2]);
687
688
emitter.resume();
689
assert.deepStrictEqual(data, [1, 2, 3, 4]);
690
emitter.fire(5);
691
assert.deepStrictEqual(data, [1, 2, 3, 4, 5]);
692
});
693
694
test('pause/resume - merge', function () {
695
const data: number[] = [];
696
const emitter = ds.add(new PauseableEmitter<number>({ merge: (a) => a.reduce((p, c) => p + c, 0) }));
697
698
ds.add(emitter.event(e => data.push(e)));
699
emitter.fire(1);
700
emitter.fire(2);
701
assert.deepStrictEqual(data, [1, 2]);
702
703
emitter.pause();
704
emitter.fire(3);
705
emitter.fire(4);
706
assert.deepStrictEqual(data, [1, 2]);
707
708
emitter.resume();
709
assert.deepStrictEqual(data, [1, 2, 7]);
710
711
emitter.fire(5);
712
assert.deepStrictEqual(data, [1, 2, 7, 5]);
713
});
714
715
test('double pause/resume', function () {
716
const data: number[] = [];
717
const emitter = ds.add(new PauseableEmitter<number>());
718
719
ds.add(emitter.event(e => data.push(e)));
720
emitter.fire(1);
721
emitter.fire(2);
722
assert.deepStrictEqual(data, [1, 2]);
723
724
emitter.pause();
725
emitter.pause();
726
emitter.fire(3);
727
emitter.fire(4);
728
assert.deepStrictEqual(data, [1, 2]);
729
730
emitter.resume();
731
assert.deepStrictEqual(data, [1, 2]);
732
733
emitter.resume();
734
assert.deepStrictEqual(data, [1, 2, 3, 4]);
735
736
emitter.resume();
737
assert.deepStrictEqual(data, [1, 2, 3, 4]);
738
});
739
740
test('resume, no pause', function () {
741
const data: number[] = [];
742
const emitter = ds.add(new PauseableEmitter<number>());
743
744
ds.add(emitter.event(e => data.push(e)));
745
emitter.fire(1);
746
emitter.fire(2);
747
assert.deepStrictEqual(data, [1, 2]);
748
749
emitter.resume();
750
emitter.fire(3);
751
assert.deepStrictEqual(data, [1, 2, 3]);
752
});
753
754
test('nested pause', function () {
755
const data: number[] = [];
756
const emitter = ds.add(new PauseableEmitter<number>());
757
758
let once = true;
759
ds.add(emitter.event(e => {
760
data.push(e);
761
762
if (once) {
763
emitter.pause();
764
once = false;
765
}
766
}));
767
ds.add(emitter.event(e => {
768
data.push(e);
769
}));
770
771
emitter.pause();
772
emitter.fire(1);
773
emitter.fire(2);
774
assert.deepStrictEqual(data, []);
775
776
emitter.resume();
777
assert.deepStrictEqual(data, [1, 1]); // paused after first event
778
779
emitter.resume();
780
assert.deepStrictEqual(data, [1, 1, 2, 2]); // remaing event delivered
781
782
emitter.fire(3);
783
assert.deepStrictEqual(data, [1, 1, 2, 2, 3, 3]);
784
785
});
786
787
test('empty pause with merge', function () {
788
const data: number[] = [];
789
const emitter = ds.add(new PauseableEmitter<number>({ merge: a => a[0] }));
790
ds.add(emitter.event(e => data.push(1)));
791
792
emitter.pause();
793
emitter.resume();
794
assert.deepStrictEqual(data, []);
795
});
796
797
});
798
799
suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () {
800
ensureNoDisposablesAreLeakedInTestSuite();
801
802
test('fromObservable', function () {
803
804
const obs = observableValue('test', 12);
805
const event = Event.fromObservable(obs);
806
807
const values: number[] = [];
808
const d = event(n => { values.push(n); });
809
810
obs.set(3, undefined);
811
obs.set(13, undefined);
812
obs.set(3, undefined);
813
obs.set(33, undefined);
814
obs.set(1, undefined);
815
816
transaction(tx => {
817
obs.set(334, tx);
818
obs.set(99, tx);
819
});
820
821
assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99]));
822
d.dispose();
823
});
824
});
825
826
suite('Event utils', () => {
827
828
const ds = ensureNoDisposablesAreLeakedInTestSuite();
829
830
suite('EventBufferer', () => {
831
832
test('should not buffer when not wrapped', () => {
833
const bufferer = new EventBufferer();
834
const counter = new Samples.EventCounter();
835
const emitter = ds.add(new Emitter<void>());
836
const event = bufferer.wrapEvent(emitter.event);
837
const listener = event(counter.onEvent, counter);
838
839
assert.strictEqual(counter.count, 0);
840
emitter.fire();
841
assert.strictEqual(counter.count, 1);
842
emitter.fire();
843
assert.strictEqual(counter.count, 2);
844
emitter.fire();
845
assert.strictEqual(counter.count, 3);
846
847
listener.dispose();
848
});
849
850
test('should buffer when wrapped', () => {
851
const bufferer = new EventBufferer();
852
const counter = new Samples.EventCounter();
853
const emitter = ds.add(new Emitter<void>());
854
const event = bufferer.wrapEvent(emitter.event);
855
const listener = event(counter.onEvent, counter);
856
857
assert.strictEqual(counter.count, 0);
858
emitter.fire();
859
assert.strictEqual(counter.count, 1);
860
861
bufferer.bufferEvents(() => {
862
emitter.fire();
863
assert.strictEqual(counter.count, 1);
864
emitter.fire();
865
assert.strictEqual(counter.count, 1);
866
});
867
868
assert.strictEqual(counter.count, 3);
869
emitter.fire();
870
assert.strictEqual(counter.count, 4);
871
872
listener.dispose();
873
});
874
875
test('once', () => {
876
const emitter = ds.add(new Emitter<void>());
877
878
let counter1 = 0, counter2 = 0, counter3 = 0;
879
880
const listener1 = emitter.event(() => counter1++);
881
const listener2 = Event.once(emitter.event)(() => counter2++);
882
const listener3 = Event.once(emitter.event)(() => counter3++);
883
884
assert.strictEqual(counter1, 0);
885
assert.strictEqual(counter2, 0);
886
assert.strictEqual(counter3, 0);
887
888
listener3.dispose();
889
emitter.fire();
890
assert.strictEqual(counter1, 1);
891
assert.strictEqual(counter2, 1);
892
assert.strictEqual(counter3, 0);
893
894
emitter.fire();
895
assert.strictEqual(counter1, 2);
896
assert.strictEqual(counter2, 1);
897
assert.strictEqual(counter3, 0);
898
899
listener1.dispose();
900
listener2.dispose();
901
});
902
});
903
904
suite('buffer', () => {
905
906
test('should buffer events', () => {
907
const result: number[] = [];
908
const emitter = ds.add(new Emitter<number>());
909
const event = emitter.event;
910
const bufferedEvent = Event.buffer(event);
911
912
emitter.fire(1);
913
emitter.fire(2);
914
emitter.fire(3);
915
assert.deepStrictEqual(result, [] as number[]);
916
917
const listener = bufferedEvent(num => result.push(num));
918
assert.deepStrictEqual(result, [1, 2, 3]);
919
920
emitter.fire(4);
921
assert.deepStrictEqual(result, [1, 2, 3, 4]);
922
923
listener.dispose();
924
emitter.fire(5);
925
assert.deepStrictEqual(result, [1, 2, 3, 4]);
926
});
927
928
test('should buffer events on next tick', async () => {
929
const result: number[] = [];
930
const emitter = ds.add(new Emitter<number>());
931
const event = emitter.event;
932
const bufferedEvent = Event.buffer(event, true);
933
934
emitter.fire(1);
935
emitter.fire(2);
936
emitter.fire(3);
937
assert.deepStrictEqual(result, [] as number[]);
938
939
const listener = bufferedEvent(num => result.push(num));
940
assert.deepStrictEqual(result, []);
941
942
await timeout(10);
943
emitter.fire(4);
944
assert.deepStrictEqual(result, [1, 2, 3, 4]);
945
listener.dispose();
946
emitter.fire(5);
947
assert.deepStrictEqual(result, [1, 2, 3, 4]);
948
});
949
950
test('should fire initial buffer events', () => {
951
const result: number[] = [];
952
const emitter = ds.add(new Emitter<number>());
953
const event = emitter.event;
954
const bufferedEvent = Event.buffer(event, false, [-2, -1, 0]);
955
956
emitter.fire(1);
957
emitter.fire(2);
958
emitter.fire(3);
959
assert.deepStrictEqual(result, [] as number[]);
960
961
ds.add(bufferedEvent(num => result.push(num)));
962
assert.deepStrictEqual(result, [-2, -1, 0, 1, 2, 3]);
963
});
964
});
965
966
suite('EventMultiplexer', () => {
967
968
test('works', () => {
969
const result: number[] = [];
970
const m = new EventMultiplexer<number>();
971
ds.add(m.event(r => result.push(r)));
972
973
const e1 = ds.add(new Emitter<number>());
974
ds.add(m.add(e1.event));
975
976
assert.deepStrictEqual(result, []);
977
978
e1.fire(0);
979
assert.deepStrictEqual(result, [0]);
980
});
981
982
test('multiplexer dispose works', () => {
983
const result: number[] = [];
984
const m = new EventMultiplexer<number>();
985
ds.add(m.event(r => result.push(r)));
986
987
const e1 = ds.add(new Emitter<number>());
988
ds.add(m.add(e1.event));
989
990
assert.deepStrictEqual(result, []);
991
992
e1.fire(0);
993
assert.deepStrictEqual(result, [0]);
994
995
m.dispose();
996
assert.deepStrictEqual(result, [0]);
997
998
e1.fire(0);
999
assert.deepStrictEqual(result, [0]);
1000
});
1001
1002
test('event dispose works', () => {
1003
const result: number[] = [];
1004
const m = new EventMultiplexer<number>();
1005
ds.add(m.event(r => result.push(r)));
1006
1007
const e1 = ds.add(new Emitter<number>());
1008
ds.add(m.add(e1.event));
1009
1010
assert.deepStrictEqual(result, []);
1011
1012
e1.fire(0);
1013
assert.deepStrictEqual(result, [0]);
1014
1015
e1.dispose();
1016
assert.deepStrictEqual(result, [0]);
1017
1018
e1.fire(0);
1019
assert.deepStrictEqual(result, [0]);
1020
});
1021
1022
test('mutliplexer event dispose works', () => {
1023
const result: number[] = [];
1024
const m = new EventMultiplexer<number>();
1025
ds.add(m.event(r => result.push(r)));
1026
1027
const e1 = ds.add(new Emitter<number>());
1028
const l1 = m.add(e1.event);
1029
1030
assert.deepStrictEqual(result, []);
1031
1032
e1.fire(0);
1033
assert.deepStrictEqual(result, [0]);
1034
1035
l1.dispose();
1036
assert.deepStrictEqual(result, [0]);
1037
1038
e1.fire(0);
1039
assert.deepStrictEqual(result, [0]);
1040
});
1041
1042
test('hot start works', () => {
1043
const result: number[] = [];
1044
const m = new EventMultiplexer<number>();
1045
ds.add(m.event(r => result.push(r)));
1046
1047
const e1 = ds.add(new Emitter<number>());
1048
ds.add(m.add(e1.event));
1049
const e2 = ds.add(new Emitter<number>());
1050
ds.add(m.add(e2.event));
1051
const e3 = ds.add(new Emitter<number>());
1052
ds.add(m.add(e3.event));
1053
1054
e1.fire(1);
1055
e2.fire(2);
1056
e3.fire(3);
1057
assert.deepStrictEqual(result, [1, 2, 3]);
1058
});
1059
1060
test('cold start works', () => {
1061
const result: number[] = [];
1062
const m = new EventMultiplexer<number>();
1063
1064
const e1 = ds.add(new Emitter<number>());
1065
ds.add(m.add(e1.event));
1066
const e2 = ds.add(new Emitter<number>());
1067
ds.add(m.add(e2.event));
1068
const e3 = ds.add(new Emitter<number>());
1069
ds.add(m.add(e3.event));
1070
1071
ds.add(m.event(r => result.push(r)));
1072
1073
e1.fire(1);
1074
e2.fire(2);
1075
e3.fire(3);
1076
assert.deepStrictEqual(result, [1, 2, 3]);
1077
});
1078
1079
test('late add works', () => {
1080
const result: number[] = [];
1081
const m = new EventMultiplexer<number>();
1082
1083
const e1 = ds.add(new Emitter<number>());
1084
ds.add(m.add(e1.event));
1085
const e2 = ds.add(new Emitter<number>());
1086
ds.add(m.add(e2.event));
1087
1088
ds.add(m.event(r => result.push(r)));
1089
1090
e1.fire(1);
1091
e2.fire(2);
1092
1093
const e3 = ds.add(new Emitter<number>());
1094
ds.add(m.add(e3.event));
1095
e3.fire(3);
1096
1097
assert.deepStrictEqual(result, [1, 2, 3]);
1098
});
1099
1100
test('add dispose works', () => {
1101
const result: number[] = [];
1102
const m = new EventMultiplexer<number>();
1103
1104
const e1 = ds.add(new Emitter<number>());
1105
ds.add(m.add(e1.event));
1106
const e2 = ds.add(new Emitter<number>());
1107
ds.add(m.add(e2.event));
1108
1109
ds.add(m.event(r => result.push(r)));
1110
1111
e1.fire(1);
1112
e2.fire(2);
1113
1114
const e3 = ds.add(new Emitter<number>());
1115
const l3 = m.add(e3.event);
1116
e3.fire(3);
1117
assert.deepStrictEqual(result, [1, 2, 3]);
1118
1119
l3.dispose();
1120
e3.fire(4);
1121
assert.deepStrictEqual(result, [1, 2, 3]);
1122
1123
e2.fire(4);
1124
e1.fire(5);
1125
assert.deepStrictEqual(result, [1, 2, 3, 4, 5]);
1126
});
1127
});
1128
1129
suite('DynamicListEventMultiplexer', () => {
1130
let addEmitter: Emitter<TestItem>;
1131
let removeEmitter: Emitter<TestItem>;
1132
const recordedEvents: number[] = [];
1133
class TestItem {
1134
readonly onTestEventEmitter = ds.add(new Emitter<number>());
1135
readonly onTestEvent = this.onTestEventEmitter.event;
1136
}
1137
let items: TestItem[];
1138
let m: DynamicListEventMultiplexer<TestItem, number>;
1139
setup(() => {
1140
addEmitter = ds.add(new Emitter<TestItem>());
1141
removeEmitter = ds.add(new Emitter<TestItem>());
1142
items = [new TestItem(), new TestItem()];
1143
for (const [i, item] of items.entries()) {
1144
ds.add(item.onTestEvent(e => `${i}:${e}`));
1145
}
1146
m = new DynamicListEventMultiplexer(items, addEmitter.event, removeEmitter.event, e => e.onTestEvent);
1147
ds.add(m.event(e => recordedEvents.push(e)));
1148
recordedEvents.length = 0;
1149
});
1150
teardown(() => m.dispose());
1151
test('should fire events for initial items', () => {
1152
items[0].onTestEventEmitter.fire(1);
1153
items[1].onTestEventEmitter.fire(2);
1154
items[0].onTestEventEmitter.fire(3);
1155
items[1].onTestEventEmitter.fire(4);
1156
assert.deepStrictEqual(recordedEvents, [1, 2, 3, 4]);
1157
});
1158
test('should fire events for added items', () => {
1159
const addedItem = new TestItem();
1160
addEmitter.fire(addedItem);
1161
addedItem.onTestEventEmitter.fire(1);
1162
items[0].onTestEventEmitter.fire(2);
1163
items[1].onTestEventEmitter.fire(3);
1164
addedItem.onTestEventEmitter.fire(4);
1165
assert.deepStrictEqual(recordedEvents, [1, 2, 3, 4]);
1166
});
1167
test('should not fire events for removed items', () => {
1168
removeEmitter.fire(items[0]);
1169
items[0].onTestEventEmitter.fire(1);
1170
items[1].onTestEventEmitter.fire(2);
1171
items[0].onTestEventEmitter.fire(3);
1172
items[1].onTestEventEmitter.fire(4);
1173
assert.deepStrictEqual(recordedEvents, [2, 4]);
1174
});
1175
});
1176
1177
test('latch', () => {
1178
const emitter = ds.add(new Emitter<number>());
1179
const event = Event.latch(emitter.event);
1180
1181
const result: number[] = [];
1182
const listener = ds.add(event(num => result.push(num)));
1183
1184
assert.deepStrictEqual(result, []);
1185
1186
emitter.fire(1);
1187
assert.deepStrictEqual(result, [1]);
1188
1189
emitter.fire(2);
1190
assert.deepStrictEqual(result, [1, 2]);
1191
1192
emitter.fire(2);
1193
assert.deepStrictEqual(result, [1, 2]);
1194
1195
emitter.fire(1);
1196
assert.deepStrictEqual(result, [1, 2, 1]);
1197
1198
emitter.fire(1);
1199
assert.deepStrictEqual(result, [1, 2, 1]);
1200
1201
emitter.fire(3);
1202
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1203
1204
emitter.fire(3);
1205
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1206
1207
emitter.fire(3);
1208
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1209
1210
listener.dispose();
1211
});
1212
1213
test('dispose is reentrant', () => {
1214
const emitter = ds.add(new Emitter<number>({
1215
onDidRemoveLastListener: () => {
1216
emitter.dispose();
1217
}
1218
}));
1219
1220
const listener = emitter.event(() => undefined);
1221
listener.dispose(); // should not crash
1222
});
1223
1224
suite('Relay', () => {
1225
test('should input work', () => {
1226
const e1 = ds.add(new Emitter<number>());
1227
const e2 = ds.add(new Emitter<number>());
1228
const relay = new Relay<number>();
1229
1230
const result: number[] = [];
1231
const listener = (num: number) => result.push(num);
1232
const subscription = relay.event(listener);
1233
1234
e1.fire(1);
1235
assert.deepStrictEqual(result, []);
1236
1237
relay.input = e1.event;
1238
e1.fire(2);
1239
assert.deepStrictEqual(result, [2]);
1240
1241
relay.input = e2.event;
1242
e1.fire(3);
1243
e2.fire(4);
1244
assert.deepStrictEqual(result, [2, 4]);
1245
1246
subscription.dispose();
1247
e1.fire(5);
1248
e2.fire(6);
1249
assert.deepStrictEqual(result, [2, 4]);
1250
});
1251
1252
test('should Relay dispose work', () => {
1253
const e1 = ds.add(new Emitter<number>());
1254
const e2 = ds.add(new Emitter<number>());
1255
const relay = new Relay<number>();
1256
1257
const result: number[] = [];
1258
const listener = (num: number) => result.push(num);
1259
ds.add(relay.event(listener));
1260
1261
e1.fire(1);
1262
assert.deepStrictEqual(result, []);
1263
1264
relay.input = e1.event;
1265
e1.fire(2);
1266
assert.deepStrictEqual(result, [2]);
1267
1268
relay.input = e2.event;
1269
e1.fire(3);
1270
e2.fire(4);
1271
assert.deepStrictEqual(result, [2, 4]);
1272
1273
relay.dispose();
1274
e1.fire(5);
1275
e2.fire(6);
1276
assert.deepStrictEqual(result, [2, 4]);
1277
});
1278
});
1279
1280
suite('accumulate', () => {
1281
test('should not fire after a listener is disposed with undefined or []', async () => {
1282
const eventEmitter = ds.add(new Emitter<number>());
1283
const event = eventEmitter.event;
1284
const accumulated = Event.accumulate(event, 0);
1285
1286
const calls1: number[][] = [];
1287
const calls2: number[][] = [];
1288
const listener1 = ds.add(accumulated((e) => calls1.push(e)));
1289
ds.add(accumulated((e) => calls2.push(e)));
1290
1291
eventEmitter.fire(1);
1292
await timeout(1);
1293
assert.deepStrictEqual(calls1, [[1]]);
1294
assert.deepStrictEqual(calls2, [[1]]);
1295
1296
listener1.dispose();
1297
await timeout(1);
1298
assert.deepStrictEqual(calls1, [[1]]);
1299
assert.deepStrictEqual(calls2, [[1]], 'should not fire after a listener is disposed with undefined or []');
1300
});
1301
test('should accumulate a single event', async () => {
1302
const eventEmitter = ds.add(new Emitter<number>());
1303
const event = eventEmitter.event;
1304
const accumulated = Event.accumulate(event, 0);
1305
1306
const results1 = await new Promise<number[]>(r => {
1307
ds.add(accumulated(r));
1308
eventEmitter.fire(1);
1309
});
1310
assert.deepStrictEqual(results1, [1]);
1311
1312
const results2 = await new Promise<number[]>(r => {
1313
ds.add(accumulated(r));
1314
eventEmitter.fire(2);
1315
});
1316
assert.deepStrictEqual(results2, [2]);
1317
});
1318
test('should accumulate multiple events', async () => {
1319
const eventEmitter = ds.add(new Emitter<number>());
1320
const event = eventEmitter.event;
1321
const accumulated = Event.accumulate(event, 0);
1322
1323
const results1 = await new Promise<number[]>(r => {
1324
ds.add(accumulated(r));
1325
eventEmitter.fire(1);
1326
eventEmitter.fire(2);
1327
eventEmitter.fire(3);
1328
});
1329
assert.deepStrictEqual(results1, [1, 2, 3]);
1330
1331
const results2 = await new Promise<number[]>(r => {
1332
ds.add(accumulated(r));
1333
eventEmitter.fire(4);
1334
eventEmitter.fire(5);
1335
eventEmitter.fire(6);
1336
eventEmitter.fire(7);
1337
eventEmitter.fire(8);
1338
});
1339
assert.deepStrictEqual(results2, [4, 5, 6, 7, 8]);
1340
});
1341
});
1342
1343
suite('debounce', () => {
1344
test('simple', function (done: () => void) {
1345
const doc = ds.add(new Samples.Document3());
1346
1347
const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
1348
if (!prev) {
1349
prev = [cur];
1350
} else if (prev.indexOf(cur) < 0) {
1351
prev.push(cur);
1352
}
1353
return prev;
1354
}, 10);
1355
1356
let count = 0;
1357
1358
ds.add(onDocDidChange(keys => {
1359
count++;
1360
assert.ok(keys, 'was not expecting keys.');
1361
if (count === 1) {
1362
doc.setText('4');
1363
assert.deepStrictEqual(keys, ['1', '2', '3']);
1364
} else if (count === 2) {
1365
assert.deepStrictEqual(keys, ['4']);
1366
done();
1367
}
1368
}));
1369
1370
doc.setText('1');
1371
doc.setText('2');
1372
doc.setText('3');
1373
});
1374
1375
1376
test('microtask', function (done: () => void) {
1377
const doc = ds.add(new Samples.Document3());
1378
1379
const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
1380
if (!prev) {
1381
prev = [cur];
1382
} else if (prev.indexOf(cur) < 0) {
1383
prev.push(cur);
1384
}
1385
return prev;
1386
}, MicrotaskDelay);
1387
1388
let count = 0;
1389
1390
ds.add(onDocDidChange(keys => {
1391
count++;
1392
assert.ok(keys, 'was not expecting keys.');
1393
if (count === 1) {
1394
doc.setText('4');
1395
assert.deepStrictEqual(keys, ['1', '2', '3']);
1396
} else if (count === 2) {
1397
assert.deepStrictEqual(keys, ['4']);
1398
done();
1399
}
1400
}));
1401
1402
doc.setText('1');
1403
doc.setText('2');
1404
doc.setText('3');
1405
});
1406
1407
1408
test('leading', async function () {
1409
const emitter = ds.add(new Emitter<void>());
1410
const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true);
1411
1412
let calls = 0;
1413
ds.add(debounced(() => {
1414
calls++;
1415
}));
1416
1417
// If the source event is fired once, the debounced (on the leading edge) event should be fired only once
1418
emitter.fire();
1419
1420
await timeout(1);
1421
assert.strictEqual(calls, 1);
1422
});
1423
1424
test('leading (2)', async function () {
1425
const emitter = ds.add(new Emitter<void>());
1426
const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true);
1427
1428
let calls = 0;
1429
ds.add(debounced(() => {
1430
calls++;
1431
}));
1432
1433
// If the source event is fired multiple times, the debounced (on the leading edge) event should be fired twice
1434
emitter.fire();
1435
emitter.fire();
1436
emitter.fire();
1437
await timeout(1);
1438
assert.strictEqual(calls, 2);
1439
});
1440
1441
test('leading reset', async function () {
1442
const emitter = ds.add(new Emitter<number>());
1443
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true);
1444
1445
const calls: number[] = [];
1446
ds.add(debounced((e) => calls.push(e)));
1447
1448
emitter.fire(1);
1449
emitter.fire(1);
1450
1451
await timeout(1);
1452
assert.deepStrictEqual(calls, [1, 1]);
1453
});
1454
1455
test('should not flush events when a listener is disposed', async () => {
1456
const emitter = ds.add(new Emitter<number>());
1457
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0);
1458
1459
const calls: number[] = [];
1460
const listener = ds.add(debounced((e) => calls.push(e)));
1461
1462
emitter.fire(1);
1463
listener.dispose();
1464
1465
emitter.fire(1);
1466
1467
await timeout(1);
1468
assert.deepStrictEqual(calls, []);
1469
});
1470
1471
test('flushOnListenerRemove - should flush events when a listener is disposed', async () => {
1472
const emitter = ds.add(new Emitter<number>());
1473
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, undefined, true);
1474
1475
const calls: number[] = [];
1476
const listener = ds.add(debounced((e) => calls.push(e)));
1477
1478
emitter.fire(1);
1479
listener.dispose();
1480
1481
emitter.fire(1);
1482
1483
await timeout(1);
1484
assert.deepStrictEqual(calls, [1], 'should fire with the first event, not the second (after listener dispose)');
1485
});
1486
1487
test('should flush events when the emitter is disposed', async () => {
1488
const emitter = ds.add(new Emitter<number>());
1489
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0);
1490
1491
const calls: number[] = [];
1492
ds.add(debounced((e) => calls.push(e)));
1493
1494
emitter.fire(1);
1495
emitter.dispose();
1496
1497
await timeout(1);
1498
assert.deepStrictEqual(calls, [1]);
1499
});
1500
});
1501
1502
test('issue #230401', () => {
1503
let count = 0;
1504
const emitter = ds.add(new Emitter<void>());
1505
const disposables = ds.add(new DisposableStore());
1506
ds.add(emitter.event(() => {
1507
count++;
1508
disposables.add(emitter.event(() => {
1509
count++;
1510
}));
1511
disposables.add(emitter.event(() => {
1512
count++;
1513
}));
1514
disposables.clear();
1515
}));
1516
ds.add(emitter.event(() => {
1517
count++;
1518
}));
1519
emitter.fire();
1520
assert.deepStrictEqual(count, 2);
1521
});
1522
1523
suite('chain2', () => {
1524
let em: Emitter<number>;
1525
let calls: number[];
1526
1527
setup(() => {
1528
em = ds.add(new Emitter<number>());
1529
calls = [];
1530
});
1531
1532
test('maps', () => {
1533
const ev = Event.chain(em.event, $ => $.map(v => v * 2));
1534
ds.add(ev(v => calls.push(v)));
1535
em.fire(1);
1536
em.fire(2);
1537
em.fire(3);
1538
assert.deepStrictEqual(calls, [2, 4, 6]);
1539
});
1540
1541
test('filters', () => {
1542
const ev = Event.chain(em.event, $ => $.filter(v => v % 2 === 0));
1543
ds.add(ev(v => calls.push(v)));
1544
em.fire(1);
1545
em.fire(2);
1546
em.fire(3);
1547
em.fire(4);
1548
assert.deepStrictEqual(calls, [2, 4]);
1549
});
1550
1551
test('reduces', () => {
1552
const ev = Event.chain(em.event, $ => $.reduce((acc, v) => acc + v, 0));
1553
ds.add(ev(v => calls.push(v)));
1554
em.fire(1);
1555
em.fire(2);
1556
em.fire(3);
1557
em.fire(4);
1558
assert.deepStrictEqual(calls, [1, 3, 6, 10]);
1559
});
1560
1561
test('latches', () => {
1562
const ev = Event.chain(em.event, $ => $.latch());
1563
ds.add(ev(v => calls.push(v)));
1564
em.fire(1);
1565
em.fire(1);
1566
em.fire(2);
1567
em.fire(2);
1568
em.fire(3);
1569
em.fire(3);
1570
em.fire(1);
1571
assert.deepStrictEqual(calls, [1, 2, 3, 1]);
1572
});
1573
1574
test('does everything', () => {
1575
const ev = Event.chain(em.event, $ => $
1576
.filter(v => v % 2 === 0)
1577
.map(v => v * 2)
1578
.reduce((acc, v) => acc + v, 0)
1579
.latch()
1580
);
1581
1582
ds.add(ev(v => calls.push(v)));
1583
em.fire(1);
1584
em.fire(2);
1585
em.fire(3);
1586
em.fire(4);
1587
em.fire(0);
1588
assert.deepStrictEqual(calls, [4, 12]);
1589
});
1590
});
1591
});
1592
1593