188 lines
4.7 KiB
Dart
188 lines
4.7 KiB
Dart
import 'package:fake_async/fake_async.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:marianum_mobile/utils/debouncer.dart';
|
|
|
|
void main() {
|
|
// Each test is wrapped in fakeAsync so timers fire deterministically.
|
|
group('Debouncer.debounce', () {
|
|
test(
|
|
'runs the action once after the delay elapses without further calls',
|
|
() {
|
|
fakeAsync((async) {
|
|
var calls = 0;
|
|
Debouncer.debounce(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
|
|
async.elapse(const Duration(milliseconds: 99));
|
|
expect(calls, 0);
|
|
|
|
async.elapse(const Duration(milliseconds: 1));
|
|
expect(calls, 1);
|
|
});
|
|
},
|
|
);
|
|
|
|
test('subsequent calls within the delay reset the timer (coalesce)', () {
|
|
fakeAsync((async) {
|
|
var calls = 0;
|
|
void schedule() => Debouncer.debounce(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
|
|
schedule();
|
|
async.elapse(const Duration(milliseconds: 80));
|
|
schedule(); // resets
|
|
async.elapse(const Duration(milliseconds: 80));
|
|
schedule(); // resets
|
|
async.elapse(const Duration(milliseconds: 80));
|
|
expect(calls, 0, reason: 'each schedule() resets the timer');
|
|
|
|
async.elapse(const Duration(milliseconds: 100));
|
|
expect(calls, 1);
|
|
});
|
|
});
|
|
|
|
test('different tags are independent', () {
|
|
fakeAsync((async) {
|
|
var aCalls = 0;
|
|
var bCalls = 0;
|
|
Debouncer.debounce(
|
|
'a',
|
|
const Duration(milliseconds: 100),
|
|
() => aCalls++,
|
|
);
|
|
Debouncer.debounce(
|
|
'b',
|
|
const Duration(milliseconds: 100),
|
|
() => bCalls++,
|
|
);
|
|
|
|
async.elapse(const Duration(milliseconds: 100));
|
|
expect(aCalls, 1);
|
|
expect(bCalls, 1);
|
|
});
|
|
});
|
|
});
|
|
|
|
group('Debouncer.throttle', () {
|
|
test(
|
|
'first call runs immediately, subsequent calls within window are dropped',
|
|
() {
|
|
fakeAsync((async) {
|
|
var calls = 0;
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
expect(
|
|
calls,
|
|
1,
|
|
reason: 'throttle fires the first call synchronously',
|
|
);
|
|
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
expect(
|
|
calls,
|
|
1,
|
|
reason: 'subsequent calls within the gate are ignored',
|
|
);
|
|
|
|
async.elapse(const Duration(milliseconds: 100));
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
expect(
|
|
calls,
|
|
2,
|
|
reason: 'after the window elapses, throttle fires again',
|
|
);
|
|
});
|
|
},
|
|
);
|
|
|
|
test('different tags throttle independently', () {
|
|
fakeAsync((async) {
|
|
var aCalls = 0;
|
|
var bCalls = 0;
|
|
Debouncer.throttle(
|
|
'a',
|
|
const Duration(milliseconds: 100),
|
|
() => aCalls++,
|
|
);
|
|
Debouncer.throttle(
|
|
'b',
|
|
const Duration(milliseconds: 100),
|
|
() => bCalls++,
|
|
);
|
|
expect(aCalls, 1);
|
|
expect(bCalls, 1);
|
|
|
|
async.elapse(const Duration(milliseconds: 100));
|
|
});
|
|
});
|
|
});
|
|
|
|
group('Debouncer.cancel', () {
|
|
test('cancels a pending debounce so the action never runs', () {
|
|
fakeAsync((async) {
|
|
var calls = 0;
|
|
Debouncer.debounce(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
Debouncer.cancel('tag');
|
|
|
|
async.elapse(const Duration(milliseconds: 200));
|
|
expect(calls, 0);
|
|
});
|
|
});
|
|
|
|
test(
|
|
'cancels an active throttle gate so the next call fires immediately',
|
|
() {
|
|
fakeAsync((async) {
|
|
var calls = 0;
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
expect(calls, 1);
|
|
|
|
Debouncer.cancel('tag');
|
|
Debouncer.throttle(
|
|
'tag',
|
|
const Duration(milliseconds: 100),
|
|
() => calls++,
|
|
);
|
|
expect(
|
|
calls,
|
|
2,
|
|
reason: 'cancel removed the gate so the next throttle fires again',
|
|
);
|
|
|
|
async.elapse(const Duration(milliseconds: 100));
|
|
});
|
|
},
|
|
);
|
|
});
|
|
}
|