yx_async_throttle_flutter/test/async_throttle_comprehensiv...

1327 lines
35 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:yx_async_throttle_flutter/yx_async_throttle_flutter.dart';
/// 商用级全面测试用例
/// 覆盖所有核心功能、边界条件、并发场景、异常处理、内存安全等
void main() {
late AsyncThrottle throttle;
setUp(() {
throttle = AsyncThrottle.instance;
throttle.clearAllLocks();
});
tearDown(() {
throttle.clearAllLocks();
});
// ============================================================
// 1. 单例模式测试
// ============================================================
group('Singleton Pattern Tests', () {
test('多次获取实例应返回同一对象', () {
final instance1 = AsyncThrottle.instance;
final instance2 = AsyncThrottle.instance;
final instance3 = AsyncThrottle.instance;
expect(identical(instance1, instance2), isTrue);
expect(identical(instance2, instance3), isTrue);
});
test('实例不为null', () {
expect(AsyncThrottle.instance, isNotNull);
});
test('实例类型正确', () {
expect(AsyncThrottle.instance, isA<AsyncThrottle>());
});
});
// ============================================================
// 2. 节流模式 (Throttle) 核心功能测试
// ============================================================
group('Throttle Mode - Core Functionality', () {
test('首次调用应立即执行', () async {
int counter = 0;
final stopwatch = Stopwatch()..start();
await throttle.execute(
'throttle_immediate',
() async {
counter++;
},
enableDebounce: false,
duration: const Duration(milliseconds: 100),
);
stopwatch.stop();
expect(counter, 1);
// 应该几乎立即执行小于50ms
expect(stopwatch.elapsedMilliseconds, lessThan(50));
});
test('duration内的后续调用应被忽略', () async {
int counter = 0;
const duration = Duration(milliseconds: 200);
// 第一次调用
await throttle.execute(
'throttle_ignore',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
// 立即第二次调用(应被忽略)
await throttle.execute(
'throttle_ignore',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
expect(counter, 1);
});
test('duration过后应可以再次执行', () async {
int counter = 0;
const duration = Duration(milliseconds: 50);
await throttle.execute(
'throttle_after_duration',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
expect(counter, 1);
// 等待 duration 过去
await Future.delayed(duration + const Duration(milliseconds: 20));
await throttle.execute(
'throttle_after_duration',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
expect(counter, 2);
});
test('异步任务执行中新调用应被忽略(锁机制)', () async {
int counter = 0;
final taskCompleter = Completer<void>();
// 启动长时间任务
final future1 = throttle.execute(
'throttle_lock',
() async {
counter++;
await taskCompleter.future;
},
enableDebounce: false,
);
// 确保任务已开始
await Future.delayed(const Duration(milliseconds: 10));
expect(throttle.isExecuting('throttle_lock'), isTrue);
// 尝试再次调用(应被锁阻止)
await throttle.execute(
'throttle_lock',
() async {
counter++;
},
enableDebounce: false,
);
expect(counter, 1);
// 完成任务
taskCompleter.complete();
await future1;
expect(throttle.isExecuting('throttle_lock'), isFalse);
});
test('异常后锁应正确释放', () async {
const duration = Duration(milliseconds: 50);
bool exceptionThrown = false;
try {
await throttle.execute(
'throttle_exception_lock',
() async {
throw Exception('Test exception');
},
enableDebounce: false,
duration: duration,
);
} catch (e) {
exceptionThrown = true;
}
expect(exceptionThrown, isTrue);
expect(throttle.isExecuting('throttle_exception_lock'), isFalse);
// 等待 duration
await Future.delayed(duration + const Duration(milliseconds: 20));
// 应该可以再次执行
int counter = 0;
await throttle.execute(
'throttle_exception_lock',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
expect(counter, 1);
});
test('异常应正确传播', () async {
expect(
() => throttle.execute(
'throttle_exception_propagate',
() async {
throw StateError('Propagated error');
},
enableDebounce: false,
),
throwsA(isA<StateError>().having((e) => e.message, 'message', 'Propagated error')),
);
});
test('不同tagId应互不影响', () async {
int counter1 = 0;
int counter2 = 0;
final future1 = throttle.execute(
'tag_a',
() async {
await Future.delayed(const Duration(milliseconds: 50));
counter1++;
},
enableDebounce: false,
);
final future2 = throttle.execute(
'tag_b',
() async {
await Future.delayed(const Duration(milliseconds: 50));
counter2++;
},
enableDebounce: false,
);
await Future.wait([future1, future2]);
expect(counter1, 1);
expect(counter2, 1);
});
test('节流期间多次调用全部被忽略', () async {
int counter = 0;
const duration = Duration(milliseconds: 100);
// 第一次调用
await throttle.execute(
'throttle_multiple_ignore',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
// 快速连续多次调用
for (int i = 0; i < 10; i++) {
await throttle.execute(
'throttle_multiple_ignore',
() async {
counter++;
},
enableDebounce: false,
duration: duration,
);
}
expect(counter, 1);
});
});
// ============================================================
// 3. 防抖模式 (Debounce) 核心功能测试
// ============================================================
group('Debounce Mode - Core Functionality', () {
test('单次调用应延迟执行', () async {
int counter = 0;
const duration = Duration(milliseconds: 50);
final future = throttle.execute(
'debounce_delay',
() async {
counter++;
},
enableDebounce: true,
duration: duration,
);
// 调用后立即检查
expect(counter, 0);
await future;
expect(counter, 1);
});
test('快速连续调用只应执行最后一次', () async {
int counter = 0;
final executedValues = <int>[];
const duration = Duration(milliseconds: 50);
final futures = <Future<void>>[];
for (int i = 1; i <= 5; i++) {
final value = i;
futures.add(
throttle.execute(
'debounce_last_only',
() async {
counter++;
executedValues.add(value);
},
enableDebounce: true,
duration: duration,
),
);
}
await Future.delayed(const Duration(milliseconds: 150));
await Future.wait(futures);
expect(counter, 1);
expect(executedValues, [5]); // 只执行了最后一次
});
test('被取消调用的Future应正确完成不挂起', () async {
const duration = Duration(milliseconds: 50);
final completedFutures = <int>[];
final f1 = throttle.execute(
'debounce_cancel_complete',
() async {},
enableDebounce: true,
duration: duration,
).then((_) => completedFutures.add(1));
final f2 = throttle.execute(
'debounce_cancel_complete',
() async {},
enableDebounce: true,
duration: duration,
).then((_) => completedFutures.add(2));
final f3 = throttle.execute(
'debounce_cancel_complete',
() async {},
enableDebounce: true,
duration: duration,
).then((_) => completedFutures.add(3));
await Future.wait([f1, f2, f3]).timeout(const Duration(seconds: 2));
// 所有 Future 都应该完成
expect(completedFutures.length, 3);
});
test('任务执行中新调用应被阻止', () async {
int counter = 0;
final taskCompleter = Completer<void>();
const duration = Duration(milliseconds: 10);
// 启动第一个任务
final f1 = throttle.execute(
'debounce_lock_test',
() async {
counter++;
await taskCompleter.future;
},
enableDebounce: true,
duration: duration,
);
// 等待防抖触发并开始执行
await Future.delayed(const Duration(milliseconds: 50));
expect(throttle.isExecuting('debounce_lock_test'), isTrue);
// 尝试新调用(应被阻止)
final f2 = throttle.execute(
'debounce_lock_test',
() async {
counter++;
},
enableDebounce: true,
duration: duration,
);
await Future.delayed(const Duration(milliseconds: 50));
// 完成原任务
taskCompleter.complete();
await f1;
await f2;
expect(counter, 1);
});
test('防抖异常应正确传播到最后一个调用者', () async {
const duration = Duration(milliseconds: 30);
final f1 = throttle.execute(
'debounce_error_propagate',
() async {
// 不会执行
},
enableDebounce: true,
duration: duration,
);
final f2 = throttle.execute(
'debounce_error_propagate',
() async {
throw ArgumentError('Last call error');
},
enableDebounce: true,
duration: duration,
);
// f1 应该正常完成(被取消)
await expectLater(f1, completes);
// f2 应该抛出错误
await expectLater(f2, throwsA(isA<ArgumentError>()));
});
test('防抖异常后锁应正确释放', () async {
const duration = Duration(milliseconds: 30);
try {
await throttle.execute(
'debounce_error_lock',
() async {
throw Exception('Test');
},
enableDebounce: true,
duration: duration,
);
} catch (_) {}
expect(throttle.isExecuting('debounce_error_lock'), isFalse);
// 应该可以再次执行
int counter = 0;
await throttle.execute(
'debounce_error_lock',
() async {
counter++;
},
enableDebounce: true,
duration: duration,
);
await Future.delayed(const Duration(milliseconds: 80));
expect(counter, 1);
});
});
// ============================================================
// 4. executeSafe 安全模式测试
// ============================================================
group('ExecuteSafe - UI Safe Mode', () {
test('节流模式异常不应向外抛', () async {
Object? capturedError;
// 不应该抛出异常
await throttle.executeSafe(
'safe_throttle',
() async {
throw StateError('Should be caught');
},
enableDebounce: false,
onError: (e, s) {
capturedError = e;
},
);
expect(capturedError, isA<StateError>());
});
test('防抖模式异常不应向外抛', () async {
Object? capturedError;
StackTrace? capturedStack;
await throttle.executeSafe(
'safe_debounce',
() async {
throw FormatException('Safe debounce error');
},
enableDebounce: true,
duration: const Duration(milliseconds: 30),
onError: (e, s) {
capturedError = e;
capturedStack = s;
},
);
await Future.delayed(const Duration(milliseconds: 80));
expect(capturedError, isA<FormatException>());
expect(capturedStack, isNotNull);
});
test('onError回调正确接收错误和堆栈', () async {
Object? error;
StackTrace? stack;
await throttle.executeSafe(
'safe_error_callback',
() async {
throw UnsupportedError('Test error');
},
enableDebounce: false,
onError: (e, s) {
error = e;
stack = s;
},
);
expect(error, isA<UnsupportedError>());
expect(stack, isNotNull);
expect(stack.toString(), isNotEmpty);
});
test('无异常时onError不应被调用', () async {
bool errorCalled = false;
await throttle.executeSafe(
'safe_no_error',
() async {
// 正常执行
},
enableDebounce: false,
onError: (e, s) {
errorCalled = true;
},
);
expect(errorCalled, isFalse);
});
test('onError为null时异常被静默吞掉', () async {
// 不应该抛出任何异常
await throttle.executeSafe(
'safe_null_callback',
() async {
throw Exception('Silent error');
},
enableDebounce: false,
onError: null,
);
// 如果到达这里,测试通过
expect(true, isTrue);
});
});
// ============================================================
// 5. isExecuting 状态检查测试
// ============================================================
group('IsExecuting - State Check', () {
test('执行前应返回false', () {
expect(throttle.isExecuting('not_started'), isFalse);
});
test('节流执行中应返回true', () async {
final completer = Completer<void>();
final future = throttle.execute(
'executing_throttle',
() async {
await completer.future;
},
enableDebounce: false,
);
await Future.delayed(const Duration(milliseconds: 10));
expect(throttle.isExecuting('executing_throttle'), isTrue);
completer.complete();
await future;
expect(throttle.isExecuting('executing_throttle'), isFalse);
});
test('防抖执行中应返回true', () async {
final completer = Completer<void>();
final future = throttle.execute(
'executing_debounce',
() async {
await completer.future;
},
enableDebounce: true,
duration: const Duration(milliseconds: 10),
);
// 等待防抖触发
await Future.delayed(const Duration(milliseconds: 30));
expect(throttle.isExecuting('executing_debounce'), isTrue);
completer.complete();
await future;
expect(throttle.isExecuting('executing_debounce'), isFalse);
});
test('异常后应返回false', () async {
try {
await throttle.execute(
'executing_error',
() async {
throw Exception('Test');
},
enableDebounce: false,
);
} catch (_) {}
expect(throttle.isExecuting('executing_error'), isFalse);
});
test('不存在的tagId应返回false', () {
expect(throttle.isExecuting('non_existent_tag_12345'), isFalse);
});
});
// ============================================================
// 6. clearAllLocks 测试
// ============================================================
group('ClearAllLocks - Lock Cleanup', () {
test('应清除所有锁', () async {
final completer1 = Completer<void>();
final completer2 = Completer<void>();
throttle.execute('lock1', () async {
await completer1.future;
}, enableDebounce: false);
throttle.execute('lock2', () async {
await completer2.future;
}, enableDebounce: false);
await Future.delayed(const Duration(milliseconds: 10));
expect(throttle.isExecuting('lock1'), isTrue);
expect(throttle.isExecuting('lock2'), isTrue);
throttle.clearAllLocks();
expect(throttle.isExecuting('lock1'), isFalse);
expect(throttle.isExecuting('lock2'), isFalse);
completer1.complete();
completer2.complete();
});
test('应释放等待中的防抖Completer', () async {
final future = throttle.execute(
'pending_debounce',
() async {},
enableDebounce: true,
duration: const Duration(milliseconds: 100),
);
// 立即清除
throttle.clearAllLocks();
// Future 应该正常完成而不是挂起
await future.timeout(const Duration(seconds: 1));
});
test('清除后异步锁应解除配合duration过期后可重新执行', () async {
final completer = Completer<void>();
const duration = Duration(milliseconds: 30);
throttle.execute('reexecute', () async {
await completer.future;
}, enableDebounce: false, duration: duration);
await Future.delayed(const Duration(milliseconds: 10));
// 此时异步锁生效clearAllLocks 清除异步锁
throttle.clearAllLocks();
expect(throttle.isExecuting('reexecute'), isFalse);
// 等待 EasyThrottle 的 duration 过期
await Future.delayed(duration + const Duration(milliseconds: 20));
int counter = 0;
await throttle.execute('reexecute', () async {
counter++;
}, enableDebounce: false, duration: duration);
expect(counter, 1);
completer.complete();
});
test('多次调用clearAllLocks应安全', () {
throttle.clearAllLocks();
throttle.clearAllLocks();
throttle.clearAllLocks();
expect(true, isTrue); // 无异常则通过
});
});
// ============================================================
// 7. 并发场景测试
// ============================================================
group('Concurrency Tests', () {
test('多个不同tagId并发执行', () async {
final results = <String>[];
final futures = <Future<void>>[];
for (int i = 0; i < 10; i++) {
final tag = 'concurrent_$i';
futures.add(
throttle.execute(tag, () async {
await Future.delayed(const Duration(milliseconds: 10));
results.add(tag);
}, enableDebounce: false),
);
}
await Future.wait(futures);
expect(results.length, 10);
});
test('大量快速调用压力测试 - 节流', () async {
int counter = 0;
const iterations = 100;
final futures = <Future<void>>[];
for (int i = 0; i < iterations; i++) {
futures.add(
throttle.execute('stress_throttle', () async {
counter++;
}, enableDebounce: false, duration: const Duration(milliseconds: 500)),
);
}
await Future.wait(futures);
expect(counter, 1); // 只执行一次
});
test('大量快速调用压力测试 - 防抖', () async {
int counter = 0;
const iterations = 100;
final futures = <Future<void>>[];
for (int i = 0; i < iterations; i++) {
futures.add(
throttle.execute('stress_debounce', () async {
counter++;
}, enableDebounce: true, duration: const Duration(milliseconds: 50)),
);
}
await Future.delayed(const Duration(milliseconds: 200));
await Future.wait(futures).timeout(const Duration(seconds: 2));
expect(counter, 1); // 只执行最后一次
});
test('混合模式并发', () async {
int throttleCounter = 0;
int debounceCounter = 0;
final futures = <Future<void>>[];
// 节流调用
for (int i = 0; i < 5; i++) {
futures.add(
throttle.execute('mixed_throttle', () async {
throttleCounter++;
}, enableDebounce: false),
);
}
// 防抖调用
for (int i = 0; i < 5; i++) {
futures.add(
throttle.execute('mixed_debounce', () async {
debounceCounter++;
}, enableDebounce: true, duration: const Duration(milliseconds: 30)),
);
}
await Future.delayed(const Duration(milliseconds: 100));
await Future.wait(futures);
expect(throttleCounter, 1);
expect(debounceCounter, 1);
});
});
// ============================================================
// 8. 边界条件测试
// ============================================================
group('Edge Cases', () {
test('duration为零应正常工作', () async {
int counter = 0;
await throttle.execute(
'zero_duration',
() async {
counter++;
},
enableDebounce: false,
duration: Duration.zero,
);
expect(counter, 1);
});
test('极短duration应正常工作', () async {
int counter = 0;
await throttle.execute(
'tiny_duration',
() async {
counter++;
},
enableDebounce: false,
duration: const Duration(microseconds: 1),
);
expect(counter, 1);
});
test('空字符串tagId应正常工作', () async {
int counter = 0;
await throttle.execute(
'',
() async {
counter++;
},
enableDebounce: false,
);
expect(counter, 1);
});
test('特殊字符tagId应正常工作', () async {
int counter = 0;
const specialTag = '!@#\$%^&*()_+-=[]{}|;:,.<>?/~`中文🎉';
await throttle.execute(
specialTag,
() async {
counter++;
},
enableDebounce: false,
);
expect(counter, 1);
expect(throttle.isExecuting(specialTag), isFalse);
});
test('长字符串tagId应正常工作', () async {
int counter = 0;
final longTag = 'a' * 10000;
await throttle.execute(
longTag,
() async {
counter++;
},
enableDebounce: false,
);
expect(counter, 1);
});
test('同步完成的异步任务应正常工作', () async {
int counter = 0;
await throttle.execute(
'sync_async',
() async {
counter++; // 同步代码
},
enableDebounce: false,
);
expect(counter, 1);
});
test('嵌套异步任务应正常工作', () async {
int counter = 0;
await throttle.execute(
'nested_async',
() async {
await Future.delayed(const Duration(milliseconds: 10));
await Future.delayed(const Duration(milliseconds: 10));
await Future.delayed(const Duration(milliseconds: 10));
counter++;
},
enableDebounce: false,
);
expect(counter, 1);
});
});
// ============================================================
// 9. 重入测试
// ============================================================
group('Reentrancy Tests', () {
test('在回调中调用同一tagId应被阻止无死锁', () async {
int outerCounter = 0;
int innerCounter = 0;
await throttle.execute(
'reentrant_same',
() async {
outerCounter++;
// 尝试重入
await throttle
.execute('reentrant_same', () async {
innerCounter++;
}, enableDebounce: false)
.timeout(const Duration(seconds: 1));
},
enableDebounce: false,
);
expect(outerCounter, 1);
expect(innerCounter, 0); // 内部调用被阻止
});
test('在回调中调用不同tagId应正常执行', () async {
int outerCounter = 0;
int innerCounter = 0;
await throttle.execute(
'reentrant_outer',
() async {
outerCounter++;
await throttle.execute('reentrant_inner', () async {
innerCounter++;
}, enableDebounce: false);
},
enableDebounce: false,
);
expect(outerCounter, 1);
expect(innerCounter, 1);
});
test('防抖模式重入应安全', () async {
int outerCounter = 0;
int innerCounter = 0;
final future = throttle.execute(
'debounce_reentrant_outer',
() async {
outerCounter++;
await throttle
.execute('debounce_reentrant_outer', () async {
innerCounter++;
}, enableDebounce: true, duration: const Duration(milliseconds: 10))
.timeout(const Duration(seconds: 1));
},
enableDebounce: true,
duration: const Duration(milliseconds: 10),
);
await Future.delayed(const Duration(milliseconds: 100));
await future;
expect(outerCounter, 1);
expect(innerCounter, 0);
});
});
// ============================================================
// 10. 内存安全测试
// ============================================================
group('Memory Safety Tests', () {
test('大量防抖调用后Completer应正确清理', () async {
const iterations = 200;
for (int batch = 0; batch < 5; batch++) {
final futures = <Future<void>>[];
for (int i = 0; i < iterations; i++) {
futures.add(
throttle.execute('memory_test_$batch', () async {
// empty
}, enableDebounce: true, duration: const Duration(milliseconds: 20)),
);
}
await Future.delayed(const Duration(milliseconds: 100));
await Future.wait(futures).timeout(const Duration(seconds: 2));
}
// 如果到达这里没有超时或内存问题,测试通过
expect(true, isTrue);
});
test('异常场景下资源应正确释放', () async {
for (int i = 0; i < 50; i++) {
try {
await throttle.execute(
'exception_cleanup_$i',
() async {
throw Exception('Error $i');
},
enableDebounce: i % 2 == 0,
duration: const Duration(milliseconds: 10),
);
} catch (_) {}
}
// 所有锁应该被释放
for (int i = 0; i < 50; i++) {
expect(throttle.isExecuting('exception_cleanup_$i'), isFalse);
}
});
test('clearAllLocks后状态应完全重置', () async {
// 创建一些状态
for (int i = 0; i < 10; i++) {
throttle.execute('cleanup_$i', () async {
await Future.delayed(const Duration(seconds: 10));
}, enableDebounce: false);
}
await Future.delayed(const Duration(milliseconds: 50));
// 清除所有
throttle.clearAllLocks();
// 验证状态重置
for (int i = 0; i < 10; i++) {
expect(throttle.isExecuting('cleanup_$i'), isFalse);
}
});
});
// ============================================================
// 11. 时间精度测试
// ============================================================
group('Timing Precision Tests', () {
test('节流duration应基本准确', () async {
const duration = Duration(milliseconds: 100);
int counter = 0;
await throttle.execute('timing_throttle', () async {
counter++;
}, enableDebounce: false, duration: duration);
expect(counter, 1);
// 在 duration 之前调用应被忽略
await Future.delayed(const Duration(milliseconds: 50));
await throttle.execute('timing_throttle', () async {
counter++;
}, enableDebounce: false, duration: duration);
expect(counter, 1);
// 在 duration 之后调用应执行
await Future.delayed(const Duration(milliseconds: 80));
await throttle.execute('timing_throttle', () async {
counter++;
}, enableDebounce: false, duration: duration);
expect(counter, 2);
});
test('防抖duration应基本准确', () async {
const duration = Duration(milliseconds: 100);
int counter = 0;
final stopwatch = Stopwatch()..start();
await throttle.execute('timing_debounce', () async {
counter++;
}, enableDebounce: true, duration: duration);
stopwatch.stop();
expect(counter, 1);
// 执行时间应该大于等于 duration
expect(stopwatch.elapsedMilliseconds, greaterThanOrEqualTo(90));
});
});
// ============================================================
// 12. 异常类型测试
// ============================================================
group('Exception Type Tests', () {
test('Error类型应正确传播', () async {
await expectLater(
throttle.execute('error_type', () async {
throw AssertionError('Test assertion');
}, enableDebounce: false),
throwsA(isA<AssertionError>()),
);
});
test('String异常应正确传播', () async {
await expectLater(
throttle.execute('string_exception', () async {
throw 'String exception';
}, enableDebounce: false),
throwsA(equals('String exception')),
);
});
test('自定义异常应正确传播', () async {
await expectLater(
throttle.execute('custom_exception', () async {
throw CustomTestException('Custom error');
}, enableDebounce: false),
throwsA(isA<CustomTestException>()),
);
});
test('防抖模式异常类型应正确传播', () async {
await expectLater(
throttle.execute('debounce_exception_type', () async {
throw RangeError('Out of range');
}, enableDebounce: true, duration: const Duration(milliseconds: 10)),
throwsA(isA<RangeError>()),
);
});
});
// ============================================================
// 13. 默认参数测试
// ============================================================
group('Default Parameters Tests', () {
test('默认duration应为300ms', () async {
int counter = 0;
await throttle.execute('default_duration', () async {
counter++;
});
expect(counter, 1);
// 在默认 300ms 内再次调用应被忽略
await throttle.execute('default_duration', () async {
counter++;
});
expect(counter, 1);
});
test('默认enableDebounce应为false节流模式', () async {
int counter = 0;
final stopwatch = Stopwatch()..start();
await throttle.execute(
'default_mode',
() async {
counter++;
},
duration: const Duration(milliseconds: 100),
);
stopwatch.stop();
// 节流模式应立即执行
expect(counter, 1);
expect(stopwatch.elapsedMilliseconds, lessThan(50));
});
});
// ============================================================
// 14. 顺序保证测试
// ============================================================
group('Order Guarantee Tests', () {
test('防抖模式应保证只执行最后一次', () async {
final executedValues = <int>[];
const duration = Duration(milliseconds: 30);
final futures = <Future<void>>[];
for (int i = 1; i <= 10; i++) {
final value = i;
futures.add(
throttle.execute(
'order_test',
() async {
executedValues.add(value);
},
enableDebounce: true,
duration: duration,
),
);
await Future.delayed(const Duration(milliseconds: 5));
}
await Future.delayed(const Duration(milliseconds: 100));
await Future.wait(futures);
expect(executedValues.length, 1);
expect(executedValues.first, 10);
});
});
// ============================================================
// 15. 复杂场景集成测试
// ============================================================
group('Integration Tests', () {
test('模拟真实按钮快速点击场景', () async {
int apiCallCount = 0;
final results = <String>[];
// 模拟用户快速点击按钮5次
for (int i = 0; i < 5; i++) {
throttle.executeSafe(
'button_click',
() async {
apiCallCount++;
await Future.delayed(const Duration(milliseconds: 100)); // 模拟API调用
results.add('success');
},
enableDebounce: false,
duration: const Duration(milliseconds: 300),
);
await Future.delayed(const Duration(milliseconds: 50));
}
await Future.delayed(const Duration(milliseconds: 500));
expect(apiCallCount, 1);
expect(results.length, 1);
});
test('模拟搜索输入防抖场景', () async {
final searchQueries = <String>[];
Future<void> search(String query) async {
await throttle.execute(
'search_input',
() async {
await Future.delayed(const Duration(milliseconds: 50)); // 模拟网络请求
searchQueries.add(query);
},
enableDebounce: true,
duration: const Duration(milliseconds: 100),
);
}
// 模拟用户输入 "flutter"
search('f');
await Future.delayed(const Duration(milliseconds: 30));
search('fl');
await Future.delayed(const Duration(milliseconds: 30));
search('flu');
await Future.delayed(const Duration(milliseconds: 30));
search('flut');
await Future.delayed(const Duration(milliseconds: 30));
search('flutt');
await Future.delayed(const Duration(milliseconds: 30));
await search('flutter');
await Future.delayed(const Duration(milliseconds: 200));
// 只有最后一个搜索词被执行
expect(searchQueries.length, 1);
expect(searchQueries.first, 'flutter');
});
test('模拟弱网环境重复点击场景', () async {
int requestCount = 0;
final taskCompleter = Completer<void>();
// 第一次点击,请求开始但未返回
final f1 = throttle.execute(
'weak_network',
() async {
requestCount++;
await taskCompleter.future; // 模拟弱网,请求迟迟不返回
},
enableDebounce: false,
);
await Future.delayed(const Duration(milliseconds: 50));
// 用户不耐烦,继续点击
for (int i = 0; i < 10; i++) {
await throttle.execute(
'weak_network',
() async {
requestCount++;
},
enableDebounce: false,
);
}
// 请求返回
taskCompleter.complete();
await f1;
// 应该只有一次请求
expect(requestCount, 1);
});
test('混合使用节流和防抖应互不干扰', () async {
int throttleCount = 0;
int debounceCount = 0;
// 同一个tag先节流后防抖
await throttle.execute('mixed_tag', () async {
throttleCount++;
}, enableDebounce: false);
await Future.delayed(const Duration(milliseconds: 350));
await throttle.execute('mixed_tag', () async {
debounceCount++;
}, enableDebounce: true, duration: const Duration(milliseconds: 30));
await Future.delayed(const Duration(milliseconds: 100));
expect(throttleCount, 1);
expect(debounceCount, 1);
});
});
}
/// 自定义测试异常类
class CustomTestException implements Exception {
final String message;
CustomTestException(this.message);
@override
String toString() => 'CustomTestException: $message';
}