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
5240 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
readonly 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
suite('Event.toPromise', () => {
468
class DisposableStoreWithSize extends DisposableStore {
469
public size = 0;
470
public override add<T extends IDisposable>(o: T): T {
471
this.size++;
472
return super.add(o);
473
}
474
475
public override delete<T extends IDisposable>(o: T): void {
476
this.size--;
477
return super.delete(o);
478
}
479
}
480
test('resolves on first event', async () => {
481
const emitter = ds.add(new Emitter<number>());
482
const promise = Event.toPromise(emitter.event);
483
484
emitter.fire(42);
485
const result = await promise;
486
487
assert.strictEqual(result, 42);
488
});
489
490
test('disposes listener after resolution', async () => {
491
const emitter = ds.add(new Emitter<number>());
492
const promise = Event.toPromise(emitter.event);
493
494
emitter.fire(1);
495
await promise;
496
497
// Listener should be disposed, firing again should not affect anything
498
emitter.fire(2);
499
assert.ok(true); // No errors
500
});
501
502
test('adds to DisposableStore', async () => {
503
const emitter = ds.add(new Emitter<number>());
504
const store = ds.add(new DisposableStoreWithSize());
505
const promise = Event.toPromise(emitter.event, store);
506
507
assert.strictEqual(store.size, 1);
508
509
emitter.fire(42);
510
await promise;
511
512
// Should be removed from store after resolution
513
assert.strictEqual(store.size, 0);
514
});
515
516
test('adds to disposables array', async () => {
517
const emitter = ds.add(new Emitter<number>());
518
const disposables: IDisposable[] = [];
519
const promise = Event.toPromise(emitter.event, disposables);
520
521
assert.strictEqual(disposables.length, 1);
522
523
emitter.fire(42);
524
await promise;
525
526
// Should be removed from array after resolution
527
assert.strictEqual(disposables.length, 0);
528
});
529
530
test('cancel removes from DisposableStore', () => {
531
const emitter = ds.add(new Emitter<number>());
532
const store = ds.add(new DisposableStoreWithSize());
533
const promise = Event.toPromise(emitter.event, store);
534
535
assert.strictEqual(store.size, 1);
536
537
promise.cancel();
538
539
// Should be removed from store after cancellation
540
assert.strictEqual(store.size, 0);
541
});
542
543
test('cancel removes from disposables array', () => {
544
const emitter = ds.add(new Emitter<number>());
545
const disposables: IDisposable[] = [];
546
const promise = Event.toPromise(emitter.event, disposables);
547
548
assert.strictEqual(disposables.length, 1);
549
550
promise.cancel();
551
552
// Should be removed from array after cancellation
553
assert.strictEqual(disposables.length, 0);
554
});
555
556
test('cancel does not resolve promise', async () => {
557
const emitter = ds.add(new Emitter<number>());
558
const promise = Event.toPromise(emitter.event);
559
560
promise.cancel();
561
emitter.fire(42);
562
563
// Promise should not resolve after cancellation
564
let resolved = false;
565
promise.then(() => resolved = true);
566
567
await timeout(10);
568
assert.strictEqual(resolved, false);
569
});
570
});
571
572
test('Microtask Emitter', (done) => {
573
let count = 0;
574
assert.strictEqual(count, 0);
575
const emitter = new MicrotaskEmitter<void>();
576
const listener = emitter.event(() => {
577
count++;
578
});
579
emitter.fire();
580
assert.strictEqual(count, 0);
581
emitter.fire();
582
assert.strictEqual(count, 0);
583
// Should wait until the event loop ends and therefore be the last thing called
584
setTimeout(() => {
585
assert.strictEqual(count, 3);
586
done();
587
}, 0);
588
queueMicrotask(() => {
589
assert.strictEqual(count, 2);
590
count++;
591
listener.dispose();
592
});
593
});
594
595
test('Emitter - In Order Delivery', function () {
596
const a = ds.add(new Emitter<string>());
597
const listener2Events: string[] = [];
598
ds.add(a.event(function listener1(event) {
599
if (event === 'e1') {
600
a.fire('e2');
601
// assert that all events are delivered at this point
602
assert.deepStrictEqual(listener2Events, ['e1', 'e2']);
603
}
604
}));
605
ds.add(a.event(function listener2(event) {
606
listener2Events.push(event);
607
}));
608
a.fire('e1');
609
610
// assert that all events are delivered in order
611
assert.deepStrictEqual(listener2Events, ['e1', 'e2']);
612
});
613
614
test('Emitter, - In Order Delivery 3x', function () {
615
const a = ds.add(new Emitter<string>());
616
const listener2Events: string[] = [];
617
ds.add(a.event(function listener1(event) {
618
if (event === 'e2') {
619
a.fire('e3');
620
// assert that all events are delivered at this point
621
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
622
}
623
}));
624
ds.add(a.event(function listener1(event) {
625
if (event === 'e1') {
626
a.fire('e2');
627
// assert that all events are delivered at this point
628
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
629
}
630
}));
631
ds.add(a.event(function listener2(event) {
632
listener2Events.push(event);
633
}));
634
a.fire('e1');
635
636
// assert that all events are delivered in order
637
assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']);
638
});
639
});
640
641
suite('AsyncEmitter', function () {
642
643
const ds = ensureNoDisposablesAreLeakedInTestSuite();
644
645
test('event has waitUntil-function', async function () {
646
647
interface E extends IWaitUntil {
648
foo: boolean;
649
bar: number;
650
}
651
652
const emitter = new AsyncEmitter<E>();
653
654
ds.add(emitter.event(e => {
655
assert.strictEqual(e.foo, true);
656
assert.strictEqual(e.bar, 1);
657
assert.strictEqual(typeof e.waitUntil, 'function');
658
}));
659
660
emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None);
661
emitter.dispose();
662
});
663
664
test('sequential delivery', async function () {
665
return runWithFakedTimers({}, async function () {
666
667
interface E extends IWaitUntil {
668
foo: boolean;
669
}
670
671
let globalState = 0;
672
const emitter = new AsyncEmitter<E>();
673
674
ds.add(emitter.event(e => {
675
e.waitUntil(timeout(10).then(_ => {
676
assert.strictEqual(globalState, 0);
677
globalState += 1;
678
}));
679
}));
680
681
ds.add(emitter.event(e => {
682
e.waitUntil(timeout(1).then(_ => {
683
assert.strictEqual(globalState, 1);
684
globalState += 1;
685
}));
686
}));
687
688
await emitter.fireAsync({ foo: true }, CancellationToken.None);
689
assert.strictEqual(globalState, 2);
690
});
691
});
692
693
test('sequential, in-order delivery', async function () {
694
return runWithFakedTimers({}, async function () {
695
696
interface E extends IWaitUntil {
697
foo: number;
698
}
699
const events: number[] = [];
700
let done = false;
701
const emitter = new AsyncEmitter<E>();
702
703
// e1
704
ds.add(emitter.event(e => {
705
e.waitUntil(timeout(10).then(async _ => {
706
if (e.foo === 1) {
707
await emitter.fireAsync({ foo: 2 }, CancellationToken.None);
708
assert.deepStrictEqual(events, [1, 2]);
709
done = true;
710
}
711
}));
712
}));
713
714
// e2
715
ds.add(emitter.event(e => {
716
events.push(e.foo);
717
e.waitUntil(timeout(7));
718
}));
719
720
await emitter.fireAsync({ foo: 1 }, CancellationToken.None);
721
assert.ok(done);
722
});
723
});
724
725
test('catch errors', async function () {
726
const origErrorHandler = errorHandler.getUnexpectedErrorHandler();
727
setUnexpectedErrorHandler(() => null);
728
729
interface E extends IWaitUntil {
730
foo: boolean;
731
}
732
733
let globalState = 0;
734
const emitter = new AsyncEmitter<E>();
735
736
ds.add(emitter.event(e => {
737
globalState += 1;
738
e.waitUntil(new Promise((_r, reject) => reject(new Error())));
739
}));
740
741
ds.add(emitter.event(e => {
742
globalState += 1;
743
e.waitUntil(timeout(10));
744
e.waitUntil(timeout(20).then(() => globalState++)); // multiple `waitUntil` are supported and awaited on
745
}));
746
747
await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => {
748
assert.strictEqual(globalState, 3);
749
}).catch(e => {
750
console.log(e);
751
assert.ok(false);
752
});
753
754
setUnexpectedErrorHandler(origErrorHandler);
755
});
756
});
757
758
suite('PausableEmitter', function () {
759
760
const ds = ensureNoDisposablesAreLeakedInTestSuite();
761
762
test('basic', function () {
763
const data: number[] = [];
764
const emitter = ds.add(new PauseableEmitter<number>());
765
766
ds.add(emitter.event(e => data.push(e)));
767
emitter.fire(1);
768
emitter.fire(2);
769
770
assert.deepStrictEqual(data, [1, 2]);
771
});
772
773
test('pause/resume - no merge', function () {
774
const data: number[] = [];
775
const emitter = ds.add(new PauseableEmitter<number>());
776
777
ds.add(emitter.event(e => data.push(e)));
778
emitter.fire(1);
779
emitter.fire(2);
780
assert.deepStrictEqual(data, [1, 2]);
781
782
emitter.pause();
783
emitter.fire(3);
784
emitter.fire(4);
785
assert.deepStrictEqual(data, [1, 2]);
786
787
emitter.resume();
788
assert.deepStrictEqual(data, [1, 2, 3, 4]);
789
emitter.fire(5);
790
assert.deepStrictEqual(data, [1, 2, 3, 4, 5]);
791
});
792
793
test('pause/resume - merge', function () {
794
const data: number[] = [];
795
const emitter = ds.add(new PauseableEmitter<number>({ merge: (a) => a.reduce((p, c) => p + c, 0) }));
796
797
ds.add(emitter.event(e => data.push(e)));
798
emitter.fire(1);
799
emitter.fire(2);
800
assert.deepStrictEqual(data, [1, 2]);
801
802
emitter.pause();
803
emitter.fire(3);
804
emitter.fire(4);
805
assert.deepStrictEqual(data, [1, 2]);
806
807
emitter.resume();
808
assert.deepStrictEqual(data, [1, 2, 7]);
809
810
emitter.fire(5);
811
assert.deepStrictEqual(data, [1, 2, 7, 5]);
812
});
813
814
test('double pause/resume', function () {
815
const data: number[] = [];
816
const emitter = ds.add(new PauseableEmitter<number>());
817
818
ds.add(emitter.event(e => data.push(e)));
819
emitter.fire(1);
820
emitter.fire(2);
821
assert.deepStrictEqual(data, [1, 2]);
822
823
emitter.pause();
824
emitter.pause();
825
emitter.fire(3);
826
emitter.fire(4);
827
assert.deepStrictEqual(data, [1, 2]);
828
829
emitter.resume();
830
assert.deepStrictEqual(data, [1, 2]);
831
832
emitter.resume();
833
assert.deepStrictEqual(data, [1, 2, 3, 4]);
834
835
emitter.resume();
836
assert.deepStrictEqual(data, [1, 2, 3, 4]);
837
});
838
839
test('resume, no pause', function () {
840
const data: number[] = [];
841
const emitter = ds.add(new PauseableEmitter<number>());
842
843
ds.add(emitter.event(e => data.push(e)));
844
emitter.fire(1);
845
emitter.fire(2);
846
assert.deepStrictEqual(data, [1, 2]);
847
848
emitter.resume();
849
emitter.fire(3);
850
assert.deepStrictEqual(data, [1, 2, 3]);
851
});
852
853
test('nested pause', function () {
854
const data: number[] = [];
855
const emitter = ds.add(new PauseableEmitter<number>());
856
857
let once = true;
858
ds.add(emitter.event(e => {
859
data.push(e);
860
861
if (once) {
862
emitter.pause();
863
once = false;
864
}
865
}));
866
ds.add(emitter.event(e => {
867
data.push(e);
868
}));
869
870
emitter.pause();
871
emitter.fire(1);
872
emitter.fire(2);
873
assert.deepStrictEqual(data, []);
874
875
emitter.resume();
876
assert.deepStrictEqual(data, [1, 1]); // paused after first event
877
878
emitter.resume();
879
assert.deepStrictEqual(data, [1, 1, 2, 2]); // remaing event delivered
880
881
emitter.fire(3);
882
assert.deepStrictEqual(data, [1, 1, 2, 2, 3, 3]);
883
884
});
885
886
test('empty pause with merge', function () {
887
const data: number[] = [];
888
const emitter = ds.add(new PauseableEmitter<number>({ merge: a => a[0] }));
889
ds.add(emitter.event(e => data.push(1)));
890
891
emitter.pause();
892
emitter.resume();
893
assert.deepStrictEqual(data, []);
894
});
895
896
});
897
898
suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () {
899
ensureNoDisposablesAreLeakedInTestSuite();
900
901
test('fromObservable', function () {
902
903
const obs = observableValue('test', 12);
904
const event = Event.fromObservable(obs);
905
906
const values: number[] = [];
907
const d = event(n => { values.push(n); });
908
909
obs.set(3, undefined);
910
obs.set(13, undefined);
911
obs.set(3, undefined);
912
obs.set(33, undefined);
913
obs.set(1, undefined);
914
915
transaction(tx => {
916
obs.set(334, tx);
917
obs.set(99, tx);
918
});
919
920
assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99]));
921
d.dispose();
922
});
923
});
924
925
suite('Event utils', () => {
926
927
const ds = ensureNoDisposablesAreLeakedInTestSuite();
928
929
suite('EventBufferer', () => {
930
931
test('should not buffer when not wrapped', () => {
932
const bufferer = new EventBufferer();
933
const counter = new Samples.EventCounter();
934
const emitter = ds.add(new Emitter<void>());
935
const event = bufferer.wrapEvent(emitter.event);
936
const listener = event(counter.onEvent, counter);
937
938
assert.strictEqual(counter.count, 0);
939
emitter.fire();
940
assert.strictEqual(counter.count, 1);
941
emitter.fire();
942
assert.strictEqual(counter.count, 2);
943
emitter.fire();
944
assert.strictEqual(counter.count, 3);
945
946
listener.dispose();
947
});
948
949
test('should buffer when wrapped', () => {
950
const bufferer = new EventBufferer();
951
const counter = new Samples.EventCounter();
952
const emitter = ds.add(new Emitter<void>());
953
const event = bufferer.wrapEvent(emitter.event);
954
const listener = event(counter.onEvent, counter);
955
956
assert.strictEqual(counter.count, 0);
957
emitter.fire();
958
assert.strictEqual(counter.count, 1);
959
960
bufferer.bufferEvents(() => {
961
emitter.fire();
962
assert.strictEqual(counter.count, 1);
963
emitter.fire();
964
assert.strictEqual(counter.count, 1);
965
});
966
967
assert.strictEqual(counter.count, 3);
968
emitter.fire();
969
assert.strictEqual(counter.count, 4);
970
971
listener.dispose();
972
});
973
974
test('once', () => {
975
const emitter = ds.add(new Emitter<void>());
976
977
let counter1 = 0, counter2 = 0, counter3 = 0;
978
979
const listener1 = emitter.event(() => counter1++);
980
const listener2 = Event.once(emitter.event)(() => counter2++);
981
const listener3 = Event.once(emitter.event)(() => counter3++);
982
983
assert.strictEqual(counter1, 0);
984
assert.strictEqual(counter2, 0);
985
assert.strictEqual(counter3, 0);
986
987
listener3.dispose();
988
emitter.fire();
989
assert.strictEqual(counter1, 1);
990
assert.strictEqual(counter2, 1);
991
assert.strictEqual(counter3, 0);
992
993
emitter.fire();
994
assert.strictEqual(counter1, 2);
995
assert.strictEqual(counter2, 1);
996
assert.strictEqual(counter3, 0);
997
998
listener1.dispose();
999
listener2.dispose();
1000
});
1001
});
1002
1003
suite('buffer', () => {
1004
1005
test('should buffer events', () => {
1006
const result: number[] = [];
1007
const emitter = ds.add(new Emitter<number>());
1008
const event = emitter.event;
1009
const bufferedEvent = Event.buffer(event);
1010
1011
emitter.fire(1);
1012
emitter.fire(2);
1013
emitter.fire(3);
1014
assert.deepStrictEqual(result, [] as number[]);
1015
1016
const listener = bufferedEvent(num => result.push(num));
1017
assert.deepStrictEqual(result, [1, 2, 3]);
1018
1019
emitter.fire(4);
1020
assert.deepStrictEqual(result, [1, 2, 3, 4]);
1021
1022
listener.dispose();
1023
emitter.fire(5);
1024
assert.deepStrictEqual(result, [1, 2, 3, 4]);
1025
});
1026
1027
test('should buffer events on next tick', async () => {
1028
const result: number[] = [];
1029
const emitter = ds.add(new Emitter<number>());
1030
const event = emitter.event;
1031
const bufferedEvent = Event.buffer(event, true);
1032
1033
emitter.fire(1);
1034
emitter.fire(2);
1035
emitter.fire(3);
1036
assert.deepStrictEqual(result, [] as number[]);
1037
1038
const listener = bufferedEvent(num => result.push(num));
1039
assert.deepStrictEqual(result, []);
1040
1041
await timeout(10);
1042
emitter.fire(4);
1043
assert.deepStrictEqual(result, [1, 2, 3, 4]);
1044
listener.dispose();
1045
emitter.fire(5);
1046
assert.deepStrictEqual(result, [1, 2, 3, 4]);
1047
});
1048
1049
test('should fire initial buffer events', () => {
1050
const result: number[] = [];
1051
const emitter = ds.add(new Emitter<number>());
1052
const event = emitter.event;
1053
const bufferedEvent = Event.buffer(event, false, [-2, -1, 0]);
1054
1055
emitter.fire(1);
1056
emitter.fire(2);
1057
emitter.fire(3);
1058
assert.deepStrictEqual(result, [] as number[]);
1059
1060
ds.add(bufferedEvent(num => result.push(num)));
1061
assert.deepStrictEqual(result, [-2, -1, 0, 1, 2, 3]);
1062
});
1063
});
1064
1065
suite('EventMultiplexer', () => {
1066
1067
test('works', () => {
1068
const result: number[] = [];
1069
const m = new EventMultiplexer<number>();
1070
ds.add(m.event(r => result.push(r)));
1071
1072
const e1 = ds.add(new Emitter<number>());
1073
ds.add(m.add(e1.event));
1074
1075
assert.deepStrictEqual(result, []);
1076
1077
e1.fire(0);
1078
assert.deepStrictEqual(result, [0]);
1079
});
1080
1081
test('multiplexer dispose works', () => {
1082
const result: number[] = [];
1083
const m = new EventMultiplexer<number>();
1084
ds.add(m.event(r => result.push(r)));
1085
1086
const e1 = ds.add(new Emitter<number>());
1087
ds.add(m.add(e1.event));
1088
1089
assert.deepStrictEqual(result, []);
1090
1091
e1.fire(0);
1092
assert.deepStrictEqual(result, [0]);
1093
1094
m.dispose();
1095
assert.deepStrictEqual(result, [0]);
1096
1097
e1.fire(0);
1098
assert.deepStrictEqual(result, [0]);
1099
});
1100
1101
test('event dispose works', () => {
1102
const result: number[] = [];
1103
const m = new EventMultiplexer<number>();
1104
ds.add(m.event(r => result.push(r)));
1105
1106
const e1 = ds.add(new Emitter<number>());
1107
ds.add(m.add(e1.event));
1108
1109
assert.deepStrictEqual(result, []);
1110
1111
e1.fire(0);
1112
assert.deepStrictEqual(result, [0]);
1113
1114
e1.dispose();
1115
assert.deepStrictEqual(result, [0]);
1116
1117
e1.fire(0);
1118
assert.deepStrictEqual(result, [0]);
1119
});
1120
1121
test('mutliplexer event dispose works', () => {
1122
const result: number[] = [];
1123
const m = new EventMultiplexer<number>();
1124
ds.add(m.event(r => result.push(r)));
1125
1126
const e1 = ds.add(new Emitter<number>());
1127
const l1 = m.add(e1.event);
1128
1129
assert.deepStrictEqual(result, []);
1130
1131
e1.fire(0);
1132
assert.deepStrictEqual(result, [0]);
1133
1134
l1.dispose();
1135
assert.deepStrictEqual(result, [0]);
1136
1137
e1.fire(0);
1138
assert.deepStrictEqual(result, [0]);
1139
});
1140
1141
test('hot start works', () => {
1142
const result: number[] = [];
1143
const m = new EventMultiplexer<number>();
1144
ds.add(m.event(r => result.push(r)));
1145
1146
const e1 = ds.add(new Emitter<number>());
1147
ds.add(m.add(e1.event));
1148
const e2 = ds.add(new Emitter<number>());
1149
ds.add(m.add(e2.event));
1150
const e3 = ds.add(new Emitter<number>());
1151
ds.add(m.add(e3.event));
1152
1153
e1.fire(1);
1154
e2.fire(2);
1155
e3.fire(3);
1156
assert.deepStrictEqual(result, [1, 2, 3]);
1157
});
1158
1159
test('cold start works', () => {
1160
const result: number[] = [];
1161
const m = new EventMultiplexer<number>();
1162
1163
const e1 = ds.add(new Emitter<number>());
1164
ds.add(m.add(e1.event));
1165
const e2 = ds.add(new Emitter<number>());
1166
ds.add(m.add(e2.event));
1167
const e3 = ds.add(new Emitter<number>());
1168
ds.add(m.add(e3.event));
1169
1170
ds.add(m.event(r => result.push(r)));
1171
1172
e1.fire(1);
1173
e2.fire(2);
1174
e3.fire(3);
1175
assert.deepStrictEqual(result, [1, 2, 3]);
1176
});
1177
1178
test('late add works', () => {
1179
const result: number[] = [];
1180
const m = new EventMultiplexer<number>();
1181
1182
const e1 = ds.add(new Emitter<number>());
1183
ds.add(m.add(e1.event));
1184
const e2 = ds.add(new Emitter<number>());
1185
ds.add(m.add(e2.event));
1186
1187
ds.add(m.event(r => result.push(r)));
1188
1189
e1.fire(1);
1190
e2.fire(2);
1191
1192
const e3 = ds.add(new Emitter<number>());
1193
ds.add(m.add(e3.event));
1194
e3.fire(3);
1195
1196
assert.deepStrictEqual(result, [1, 2, 3]);
1197
});
1198
1199
test('add dispose works', () => {
1200
const result: number[] = [];
1201
const m = new EventMultiplexer<number>();
1202
1203
const e1 = ds.add(new Emitter<number>());
1204
ds.add(m.add(e1.event));
1205
const e2 = ds.add(new Emitter<number>());
1206
ds.add(m.add(e2.event));
1207
1208
ds.add(m.event(r => result.push(r)));
1209
1210
e1.fire(1);
1211
e2.fire(2);
1212
1213
const e3 = ds.add(new Emitter<number>());
1214
const l3 = m.add(e3.event);
1215
e3.fire(3);
1216
assert.deepStrictEqual(result, [1, 2, 3]);
1217
1218
l3.dispose();
1219
e3.fire(4);
1220
assert.deepStrictEqual(result, [1, 2, 3]);
1221
1222
e2.fire(4);
1223
e1.fire(5);
1224
assert.deepStrictEqual(result, [1, 2, 3, 4, 5]);
1225
});
1226
});
1227
1228
suite('DynamicListEventMultiplexer', () => {
1229
let addEmitter: Emitter<TestItem>;
1230
let removeEmitter: Emitter<TestItem>;
1231
const recordedEvents: number[] = [];
1232
class TestItem {
1233
readonly onTestEventEmitter = ds.add(new Emitter<number>());
1234
readonly onTestEvent = this.onTestEventEmitter.event;
1235
}
1236
let items: TestItem[];
1237
let m: DynamicListEventMultiplexer<TestItem, number>;
1238
setup(() => {
1239
addEmitter = ds.add(new Emitter<TestItem>());
1240
removeEmitter = ds.add(new Emitter<TestItem>());
1241
items = [new TestItem(), new TestItem()];
1242
for (const [i, item] of items.entries()) {
1243
ds.add(item.onTestEvent(e => `${i}:${e}`));
1244
}
1245
m = new DynamicListEventMultiplexer(items, addEmitter.event, removeEmitter.event, e => e.onTestEvent);
1246
ds.add(m.event(e => recordedEvents.push(e)));
1247
recordedEvents.length = 0;
1248
});
1249
teardown(() => m.dispose());
1250
test('should fire events for initial items', () => {
1251
items[0].onTestEventEmitter.fire(1);
1252
items[1].onTestEventEmitter.fire(2);
1253
items[0].onTestEventEmitter.fire(3);
1254
items[1].onTestEventEmitter.fire(4);
1255
assert.deepStrictEqual(recordedEvents, [1, 2, 3, 4]);
1256
});
1257
test('should fire events for added items', () => {
1258
const addedItem = new TestItem();
1259
addEmitter.fire(addedItem);
1260
addedItem.onTestEventEmitter.fire(1);
1261
items[0].onTestEventEmitter.fire(2);
1262
items[1].onTestEventEmitter.fire(3);
1263
addedItem.onTestEventEmitter.fire(4);
1264
assert.deepStrictEqual(recordedEvents, [1, 2, 3, 4]);
1265
});
1266
test('should not fire events for removed items', () => {
1267
removeEmitter.fire(items[0]);
1268
items[0].onTestEventEmitter.fire(1);
1269
items[1].onTestEventEmitter.fire(2);
1270
items[0].onTestEventEmitter.fire(3);
1271
items[1].onTestEventEmitter.fire(4);
1272
assert.deepStrictEqual(recordedEvents, [2, 4]);
1273
});
1274
});
1275
1276
test('latch', () => {
1277
const emitter = ds.add(new Emitter<number>());
1278
const event = Event.latch(emitter.event);
1279
1280
const result: number[] = [];
1281
const listener = ds.add(event(num => result.push(num)));
1282
1283
assert.deepStrictEqual(result, []);
1284
1285
emitter.fire(1);
1286
assert.deepStrictEqual(result, [1]);
1287
1288
emitter.fire(2);
1289
assert.deepStrictEqual(result, [1, 2]);
1290
1291
emitter.fire(2);
1292
assert.deepStrictEqual(result, [1, 2]);
1293
1294
emitter.fire(1);
1295
assert.deepStrictEqual(result, [1, 2, 1]);
1296
1297
emitter.fire(1);
1298
assert.deepStrictEqual(result, [1, 2, 1]);
1299
1300
emitter.fire(3);
1301
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1302
1303
emitter.fire(3);
1304
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1305
1306
emitter.fire(3);
1307
assert.deepStrictEqual(result, [1, 2, 1, 3]);
1308
1309
listener.dispose();
1310
});
1311
1312
test('dispose is reentrant', () => {
1313
const emitter = ds.add(new Emitter<number>({
1314
onDidRemoveLastListener: () => {
1315
emitter.dispose();
1316
}
1317
}));
1318
1319
const listener = emitter.event(() => undefined);
1320
listener.dispose(); // should not crash
1321
});
1322
1323
suite('Relay', () => {
1324
test('should input work', () => {
1325
const e1 = ds.add(new Emitter<number>());
1326
const e2 = ds.add(new Emitter<number>());
1327
const relay = new Relay<number>();
1328
1329
const result: number[] = [];
1330
const listener = (num: number) => result.push(num);
1331
const subscription = relay.event(listener);
1332
1333
e1.fire(1);
1334
assert.deepStrictEqual(result, []);
1335
1336
relay.input = e1.event;
1337
e1.fire(2);
1338
assert.deepStrictEqual(result, [2]);
1339
1340
relay.input = e2.event;
1341
e1.fire(3);
1342
e2.fire(4);
1343
assert.deepStrictEqual(result, [2, 4]);
1344
1345
subscription.dispose();
1346
e1.fire(5);
1347
e2.fire(6);
1348
assert.deepStrictEqual(result, [2, 4]);
1349
});
1350
1351
test('should Relay dispose work', () => {
1352
const e1 = ds.add(new Emitter<number>());
1353
const e2 = ds.add(new Emitter<number>());
1354
const relay = new Relay<number>();
1355
1356
const result: number[] = [];
1357
const listener = (num: number) => result.push(num);
1358
ds.add(relay.event(listener));
1359
1360
e1.fire(1);
1361
assert.deepStrictEqual(result, []);
1362
1363
relay.input = e1.event;
1364
e1.fire(2);
1365
assert.deepStrictEqual(result, [2]);
1366
1367
relay.input = e2.event;
1368
e1.fire(3);
1369
e2.fire(4);
1370
assert.deepStrictEqual(result, [2, 4]);
1371
1372
relay.dispose();
1373
e1.fire(5);
1374
e2.fire(6);
1375
assert.deepStrictEqual(result, [2, 4]);
1376
});
1377
});
1378
1379
suite('accumulate', () => {
1380
test('should not fire after a listener is disposed with undefined or []', async () => {
1381
const eventEmitter = ds.add(new Emitter<number>());
1382
const event = eventEmitter.event;
1383
const accumulated = Event.accumulate(event, 0);
1384
1385
const calls1: number[][] = [];
1386
const calls2: number[][] = [];
1387
const listener1 = ds.add(accumulated((e) => calls1.push(e)));
1388
ds.add(accumulated((e) => calls2.push(e)));
1389
1390
eventEmitter.fire(1);
1391
await timeout(1);
1392
assert.deepStrictEqual(calls1, [[1]]);
1393
assert.deepStrictEqual(calls2, [[1]]);
1394
1395
listener1.dispose();
1396
await timeout(1);
1397
assert.deepStrictEqual(calls1, [[1]]);
1398
assert.deepStrictEqual(calls2, [[1]], 'should not fire after a listener is disposed with undefined or []');
1399
});
1400
test('should accumulate a single event', async () => {
1401
const eventEmitter = ds.add(new Emitter<number>());
1402
const event = eventEmitter.event;
1403
const accumulated = Event.accumulate(event, 0);
1404
1405
const results1 = await new Promise<number[]>(r => {
1406
ds.add(accumulated(r));
1407
eventEmitter.fire(1);
1408
});
1409
assert.deepStrictEqual(results1, [1]);
1410
1411
const results2 = await new Promise<number[]>(r => {
1412
ds.add(accumulated(r));
1413
eventEmitter.fire(2);
1414
});
1415
assert.deepStrictEqual(results2, [2]);
1416
});
1417
test('should accumulate multiple events', async () => {
1418
const eventEmitter = ds.add(new Emitter<number>());
1419
const event = eventEmitter.event;
1420
const accumulated = Event.accumulate(event, 0);
1421
1422
const results1 = await new Promise<number[]>(r => {
1423
ds.add(accumulated(r));
1424
eventEmitter.fire(1);
1425
eventEmitter.fire(2);
1426
eventEmitter.fire(3);
1427
});
1428
assert.deepStrictEqual(results1, [1, 2, 3]);
1429
1430
const results2 = await new Promise<number[]>(r => {
1431
ds.add(accumulated(r));
1432
eventEmitter.fire(4);
1433
eventEmitter.fire(5);
1434
eventEmitter.fire(6);
1435
eventEmitter.fire(7);
1436
eventEmitter.fire(8);
1437
});
1438
assert.deepStrictEqual(results2, [4, 5, 6, 7, 8]);
1439
});
1440
});
1441
1442
suite('debounce', () => {
1443
test('simple', function (done: () => void) {
1444
const doc = ds.add(new Samples.Document3());
1445
1446
const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
1447
if (!prev) {
1448
prev = [cur];
1449
} else if (prev.indexOf(cur) < 0) {
1450
prev.push(cur);
1451
}
1452
return prev;
1453
}, 10);
1454
1455
let count = 0;
1456
1457
ds.add(onDocDidChange(keys => {
1458
count++;
1459
assert.ok(keys, 'was not expecting keys.');
1460
if (count === 1) {
1461
doc.setText('4');
1462
assert.deepStrictEqual(keys, ['1', '2', '3']);
1463
} else if (count === 2) {
1464
assert.deepStrictEqual(keys, ['4']);
1465
done();
1466
}
1467
}));
1468
1469
doc.setText('1');
1470
doc.setText('2');
1471
doc.setText('3');
1472
});
1473
1474
1475
test('microtask', function (done: () => void) {
1476
const doc = ds.add(new Samples.Document3());
1477
1478
const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
1479
if (!prev) {
1480
prev = [cur];
1481
} else if (prev.indexOf(cur) < 0) {
1482
prev.push(cur);
1483
}
1484
return prev;
1485
}, MicrotaskDelay);
1486
1487
let count = 0;
1488
1489
ds.add(onDocDidChange(keys => {
1490
count++;
1491
assert.ok(keys, 'was not expecting keys.');
1492
if (count === 1) {
1493
doc.setText('4');
1494
assert.deepStrictEqual(keys, ['1', '2', '3']);
1495
} else if (count === 2) {
1496
assert.deepStrictEqual(keys, ['4']);
1497
done();
1498
}
1499
}));
1500
1501
doc.setText('1');
1502
doc.setText('2');
1503
doc.setText('3');
1504
});
1505
1506
1507
test('leading', async function () {
1508
const emitter = ds.add(new Emitter<void>());
1509
const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true);
1510
1511
let calls = 0;
1512
ds.add(debounced(() => {
1513
calls++;
1514
}));
1515
1516
// If the source event is fired once, the debounced (on the leading edge) event should be fired only once
1517
emitter.fire();
1518
1519
await timeout(1);
1520
assert.strictEqual(calls, 1);
1521
});
1522
1523
test('leading (2)', async function () {
1524
const emitter = ds.add(new Emitter<void>());
1525
const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true);
1526
1527
let calls = 0;
1528
ds.add(debounced(() => {
1529
calls++;
1530
}));
1531
1532
// If the source event is fired multiple times, the debounced (on the leading edge) event should be fired twice
1533
emitter.fire();
1534
emitter.fire();
1535
emitter.fire();
1536
await timeout(1);
1537
assert.strictEqual(calls, 2);
1538
});
1539
1540
test('leading reset', async function () {
1541
const emitter = ds.add(new Emitter<number>());
1542
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true);
1543
1544
const calls: number[] = [];
1545
ds.add(debounced((e) => calls.push(e)));
1546
1547
emitter.fire(1);
1548
emitter.fire(1);
1549
1550
await timeout(1);
1551
assert.deepStrictEqual(calls, [1, 1]);
1552
});
1553
1554
test('should not flush events when a listener is disposed', async () => {
1555
const emitter = ds.add(new Emitter<number>());
1556
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0);
1557
1558
const calls: number[] = [];
1559
const listener = ds.add(debounced((e) => calls.push(e)));
1560
1561
emitter.fire(1);
1562
listener.dispose();
1563
1564
emitter.fire(1);
1565
1566
await timeout(1);
1567
assert.deepStrictEqual(calls, []);
1568
});
1569
1570
test('flushOnListenerRemove - should flush events when a listener is disposed', async () => {
1571
const emitter = ds.add(new Emitter<number>());
1572
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, undefined, true);
1573
1574
const calls: number[] = [];
1575
const listener = ds.add(debounced((e) => calls.push(e)));
1576
1577
emitter.fire(1);
1578
listener.dispose();
1579
1580
emitter.fire(1);
1581
1582
await timeout(1);
1583
assert.deepStrictEqual(calls, [1], 'should fire with the first event, not the second (after listener dispose)');
1584
});
1585
1586
test('should flush events when the emitter is disposed', async () => {
1587
const emitter = ds.add(new Emitter<number>());
1588
const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0);
1589
1590
const calls: number[] = [];
1591
ds.add(debounced((e) => calls.push(e)));
1592
1593
emitter.fire(1);
1594
emitter.dispose();
1595
1596
await timeout(1);
1597
assert.deepStrictEqual(calls, [1]);
1598
});
1599
});
1600
1601
suite('throttle', () => {
1602
test('leading only', async function () {
1603
return runWithFakedTimers({}, async function () {
1604
const emitter = ds.add(new Emitter<number>());
1605
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, 10, /*leading=*/true, /*trailing=*/false);
1606
1607
const calls: number[] = [];
1608
ds.add(throttled((e) => calls.push(e)));
1609
1610
// First event fires immediately
1611
emitter.fire(1);
1612
assert.deepStrictEqual(calls, [1]);
1613
1614
// Subsequent events during throttle period are ignored
1615
emitter.fire(2);
1616
emitter.fire(3);
1617
assert.deepStrictEqual(calls, [1]);
1618
1619
// Wait for throttle period to end
1620
await timeout(15);
1621
assert.deepStrictEqual(calls, [1], 'no trailing edge fire with trailing=false');
1622
1623
// After throttle period, next event fires immediately
1624
emitter.fire(4);
1625
assert.deepStrictEqual(calls, [1, 1]);
1626
});
1627
});
1628
1629
test('trailing only', async function () {
1630
return runWithFakedTimers({}, async function () {
1631
const emitter = ds.add(new Emitter<number>());
1632
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, 10, /*leading=*/false, /*trailing=*/true);
1633
1634
const calls: number[] = [];
1635
ds.add(throttled((e) => calls.push(e)));
1636
1637
// First event does not fire immediately
1638
emitter.fire(1);
1639
assert.deepStrictEqual(calls, []);
1640
1641
// Multiple events during throttle period
1642
emitter.fire(2);
1643
emitter.fire(3);
1644
assert.deepStrictEqual(calls, []);
1645
1646
// Wait for throttle period - should fire with accumulated value
1647
await timeout(15);
1648
assert.deepStrictEqual(calls, [3]);
1649
1650
// New events start a new throttle period
1651
emitter.fire(4);
1652
emitter.fire(5);
1653
assert.deepStrictEqual(calls, [3]);
1654
1655
await timeout(15);
1656
assert.deepStrictEqual(calls, [3, 2]);
1657
});
1658
});
1659
1660
test('both leading and trailing', async function () {
1661
return runWithFakedTimers({}, async function () {
1662
const emitter = ds.add(new Emitter<number>());
1663
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, 10, /*leading=*/true, /*trailing=*/true);
1664
1665
const calls: number[] = [];
1666
ds.add(throttled((e) => calls.push(e)));
1667
1668
// First event fires immediately (leading)
1669
emitter.fire(1);
1670
assert.deepStrictEqual(calls, [1]);
1671
1672
// Events during throttle period are accumulated
1673
emitter.fire(2);
1674
emitter.fire(3);
1675
assert.deepStrictEqual(calls, [1]);
1676
1677
// Wait for throttle period - should fire trailing edge with accumulated value
1678
await timeout(15);
1679
assert.deepStrictEqual(calls, [1, 2]);
1680
});
1681
});
1682
1683
test('only leading edge if no subsequent events', async function () {
1684
return runWithFakedTimers({}, async function () {
1685
const emitter = ds.add(new Emitter<number>());
1686
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, 10, /*leading=*/true, /*trailing=*/true);
1687
1688
const calls: number[] = [];
1689
ds.add(throttled((e) => calls.push(e)));
1690
1691
// Single event fires immediately (leading)
1692
emitter.fire(1);
1693
assert.deepStrictEqual(calls, [1]);
1694
1695
// No more events during throttle period
1696
await timeout(15);
1697
// Should not fire trailing edge since there were no more events
1698
assert.deepStrictEqual(calls, [1]);
1699
});
1700
});
1701
1702
test('microtask delay', function (done: () => void) {
1703
const emitter = ds.add(new Emitter<number>());
1704
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, MicrotaskDelay);
1705
1706
const calls: number[] = [];
1707
ds.add(throttled((e) => calls.push(e)));
1708
1709
// First event fires immediately (leading by default)
1710
emitter.fire(1);
1711
assert.deepStrictEqual(calls, [1]);
1712
1713
// Events during microtask
1714
emitter.fire(2);
1715
emitter.fire(3);
1716
assert.deepStrictEqual(calls, [1]);
1717
1718
// Check after microtask
1719
queueMicrotask(() => {
1720
// Should have fired trailing edge
1721
assert.deepStrictEqual(calls, [1, 2]);
1722
done();
1723
});
1724
});
1725
1726
test('merge function accumulates values', async function () {
1727
return runWithFakedTimers({}, async function () {
1728
const emitter = ds.add(new Emitter<number>());
1729
const throttled = Event.throttle(
1730
emitter.event,
1731
(last, cur) => (last || 0) + cur,
1732
10,
1733
/*leading=*/true,
1734
/*trailing=*/true
1735
);
1736
1737
const calls: number[] = [];
1738
ds.add(throttled((e) => calls.push(e)));
1739
1740
// First event fires immediately with value 1
1741
emitter.fire(1);
1742
assert.deepStrictEqual(calls, [1]);
1743
1744
// Accumulate more values: 2 + 3 = 5
1745
emitter.fire(2);
1746
emitter.fire(3);
1747
assert.deepStrictEqual(calls, [1]);
1748
1749
await timeout(15);
1750
// Trailing edge fires with accumulated sum
1751
assert.deepStrictEqual(calls, [1, 5]);
1752
});
1753
});
1754
1755
test('rapid consecutive throttle periods', async function () {
1756
return runWithFakedTimers({}, async function () {
1757
const emitter = ds.add(new Emitter<number>());
1758
const throttled = Event.throttle(emitter.event, (l, e) => e, 10, /*leading=*/true, /*trailing=*/true);
1759
1760
const calls: number[] = [];
1761
ds.add(throttled((e) => calls.push(e)));
1762
1763
// Period 1
1764
emitter.fire(1);
1765
emitter.fire(2);
1766
assert.deepStrictEqual(calls, [1]);
1767
1768
await timeout(15);
1769
assert.deepStrictEqual(calls, [1, 2]);
1770
1771
// Period 2
1772
emitter.fire(3);
1773
emitter.fire(4);
1774
assert.deepStrictEqual(calls, [1, 2, 3]);
1775
1776
await timeout(15);
1777
assert.deepStrictEqual(calls, [1, 2, 3, 4]);
1778
1779
// Period 3
1780
emitter.fire(5);
1781
assert.deepStrictEqual(calls, [1, 2, 3, 4, 5]);
1782
1783
await timeout(15);
1784
// No trailing fire since only one event
1785
assert.deepStrictEqual(calls, [1, 2, 3, 4, 5]);
1786
});
1787
});
1788
1789
test('default parameters', async function () {
1790
return runWithFakedTimers({}, async function () {
1791
const emitter = ds.add(new Emitter<number>());
1792
// Default: delay=100, leading=true, trailing=true
1793
const throttled = Event.throttle(emitter.event, (l, e) => e);
1794
1795
const calls: number[] = [];
1796
ds.add(throttled((e) => calls.push(e)));
1797
1798
emitter.fire(1);
1799
assert.deepStrictEqual(calls, [1], 'should fire leading edge by default');
1800
1801
emitter.fire(2);
1802
await timeout(110);
1803
assert.deepStrictEqual(calls, [1, 2], 'should fire trailing edge by default');
1804
});
1805
});
1806
1807
test('disposal cleans up', async function () {
1808
return runWithFakedTimers({}, async function () {
1809
const emitter = ds.add(new Emitter<number>());
1810
const throttled = Event.throttle(emitter.event, (l, e) => e, 10);
1811
1812
const calls: number[] = [];
1813
const listener = throttled((e) => calls.push(e));
1814
1815
emitter.fire(1);
1816
emitter.fire(2);
1817
assert.deepStrictEqual(calls, [1]);
1818
1819
listener.dispose();
1820
1821
// Events after disposal should not fire
1822
await timeout(15);
1823
emitter.fire(3);
1824
assert.deepStrictEqual(calls, [1]);
1825
});
1826
});
1827
1828
test('no events during throttle with trailing=false', async function () {
1829
return runWithFakedTimers({}, async function () {
1830
const emitter = ds.add(new Emitter<number>());
1831
const throttled = Event.throttle(emitter.event, (l, e) => l ? l + 1 : 1, 10, /*leading=*/true, /*trailing=*/false);
1832
1833
const calls: number[] = [];
1834
ds.add(throttled((e) => calls.push(e)));
1835
1836
emitter.fire(1);
1837
assert.deepStrictEqual(calls, [1]);
1838
1839
// No more events
1840
await timeout(15);
1841
assert.deepStrictEqual(calls, [1]);
1842
1843
// Next event after throttle period
1844
emitter.fire(2);
1845
assert.deepStrictEqual(calls, [1, 1]);
1846
});
1847
});
1848
1849
test('neither leading nor trailing', async function () {
1850
return runWithFakedTimers({}, async function () {
1851
const emitter = ds.add(new Emitter<number>());
1852
const throttled = Event.throttle(emitter.event, (l, e) => e, 10, /*leading=*/false, /*trailing=*/false);
1853
1854
const calls: number[] = [];
1855
ds.add(throttled((e) => calls.push(e)));
1856
1857
emitter.fire(1);
1858
emitter.fire(2);
1859
emitter.fire(3);
1860
assert.deepStrictEqual(calls, []);
1861
1862
await timeout(15);
1863
assert.deepStrictEqual(calls, [], 'no events should fire with both leading and trailing false');
1864
});
1865
});
1866
});
1867
1868
test('issue #230401', () => {
1869
let count = 0;
1870
const emitter = ds.add(new Emitter<void>());
1871
const disposables = ds.add(new DisposableStore());
1872
ds.add(emitter.event(() => {
1873
count++;
1874
disposables.add(emitter.event(() => {
1875
count++;
1876
}));
1877
disposables.add(emitter.event(() => {
1878
count++;
1879
}));
1880
disposables.clear();
1881
}));
1882
ds.add(emitter.event(() => {
1883
count++;
1884
}));
1885
emitter.fire();
1886
assert.deepStrictEqual(count, 2);
1887
});
1888
1889
suite('chain2', () => {
1890
let em: Emitter<number>;
1891
let calls: number[];
1892
1893
setup(() => {
1894
em = ds.add(new Emitter<number>());
1895
calls = [];
1896
});
1897
1898
test('maps', () => {
1899
const ev = Event.chain(em.event, $ => $.map(v => v * 2));
1900
ds.add(ev(v => calls.push(v)));
1901
em.fire(1);
1902
em.fire(2);
1903
em.fire(3);
1904
assert.deepStrictEqual(calls, [2, 4, 6]);
1905
});
1906
1907
test('filters', () => {
1908
const ev = Event.chain(em.event, $ => $.filter(v => v % 2 === 0));
1909
ds.add(ev(v => calls.push(v)));
1910
em.fire(1);
1911
em.fire(2);
1912
em.fire(3);
1913
em.fire(4);
1914
assert.deepStrictEqual(calls, [2, 4]);
1915
});
1916
1917
test('reduces', () => {
1918
const ev = Event.chain(em.event, $ => $.reduce((acc, v) => acc + v, 0));
1919
ds.add(ev(v => calls.push(v)));
1920
em.fire(1);
1921
em.fire(2);
1922
em.fire(3);
1923
em.fire(4);
1924
assert.deepStrictEqual(calls, [1, 3, 6, 10]);
1925
});
1926
1927
test('latches', () => {
1928
const ev = Event.chain(em.event, $ => $.latch());
1929
ds.add(ev(v => calls.push(v)));
1930
em.fire(1);
1931
em.fire(1);
1932
em.fire(2);
1933
em.fire(2);
1934
em.fire(3);
1935
em.fire(3);
1936
em.fire(1);
1937
assert.deepStrictEqual(calls, [1, 2, 3, 1]);
1938
});
1939
1940
test('does everything', () => {
1941
const ev = Event.chain(em.event, $ => $
1942
.filter(v => v % 2 === 0)
1943
.map(v => v * 2)
1944
.reduce((acc, v) => acc + v, 0)
1945
.latch()
1946
);
1947
1948
ds.add(ev(v => calls.push(v)));
1949
em.fire(1);
1950
em.fire(2);
1951
em.fire(3);
1952
em.fire(4);
1953
em.fire(0);
1954
assert.deepStrictEqual(calls, [4, 12]);
1955
});
1956
});
1957
});
1958
1959