diff --git a/flutter/lib/common/widgets/autocomplete.dart b/flutter/lib/common/widgets/autocomplete.dart index 40c734cea..528ab892b 100644 --- a/flutter/lib/common/widgets/autocomplete.dart +++ b/flutter/lib/common/widgets/autocomplete.dart @@ -102,11 +102,14 @@ class AllPeersLoader { Set _lastQueryOnlineIds = {}; DateTime _lastQueryOnlineTime = DateTime.fromMillisecondsSinceEpoch(0); Timer? _queryOnlineTimer; + final Future Function(List ids) _queryOnlines; + final Duration _queryOnlineDebounce; final String _listenerKey = 'AllPeersLoader'; static const String _cbQueryOnlines = 'callback_query_onlines'; static const Duration _queryOnlineInterval = Duration(seconds: 5); - static const Duration _queryOnlineDebounce = Duration(milliseconds: 300); + static const Duration _defaultQueryOnlineDebounce = + Duration(milliseconds: 300); static const int _maxQueryOnlineOptions = 20; late void Function(VoidCallback) setState; @@ -114,7 +117,12 @@ class AllPeersLoader { bool get needLoad => !_isPeersLoaded && !_isPeersLoading; bool get isPeersLoaded => _isPeersLoaded; - AllPeersLoader(); + AllPeersLoader({ + @visibleForTesting Future Function(List ids)? queryOnlines, + @visibleForTesting Duration? queryOnlineDebounce, + }) : _queryOnlines = queryOnlines ?? ((ids) => bind.queryOnlines(ids: ids)), + _queryOnlineDebounce = + queryOnlineDebounce ?? _defaultQueryOnlineDebounce; void init(void Function(VoidCallback) setState) { this.setState = setState; @@ -197,6 +205,8 @@ class AllPeersLoader { options, limit: _maxQueryOnlineOptions, ).toSet(); + _queryOnlineTimer?.cancel(); + _queryOnlineTimer = null; if (ids.isEmpty) { return; } @@ -206,13 +216,14 @@ class AllPeersLoader { return; } - _queryOnlineTimer?.cancel(); - _queryOnlineTimer = Timer(_queryOnlineDebounce, () { - _lastQueryOnlineIds = ids; - _lastQueryOnlineTime = DateTime.now(); - bind.queryOnlines(ids: ids.toList(growable: false)).catchError((e) { + _queryOnlineTimer = Timer(_queryOnlineDebounce, () async { + try { + await _queryOnlines(ids.toList(growable: false)); + _lastQueryOnlineIds = ids; + _lastQueryOnlineTime = DateTime.now(); + } catch (e) { debugPrint('query autocomplete online state failed: $e'); - }); + } }); } } diff --git a/flutter/test/autocomplete_peer_merge_test.dart b/flutter/test/autocomplete_peer_merge_test.dart index 8dc87676c..f970a03c2 100644 --- a/flutter/test/autocomplete_peer_merge_test.dart +++ b/flutter/test/autocomplete_peer_merge_test.dart @@ -80,4 +80,39 @@ void main() { expect(ids.where((id) => id == '0'), hasLength(1)); expect(ids.last, '19'); }); + + test('empty online query ids cancel pending debounce', () async { + final queriedIds = >[]; + final loader = AllPeersLoader( + queryOnlines: (ids) async { + queriedIds.add(ids); + }, + queryOnlineDebounce: Duration(milliseconds: 1), + ); + + loader.queryOnlines([_peer(id: '123456789')]); + loader.queryOnlines([]); + await Future.delayed(Duration(milliseconds: 2)); + + expect(queriedIds, isEmpty); + }); + + test('failed online query enqueue does not suppress retry', () async { + var queryCount = 0; + final loader = AllPeersLoader( + queryOnlines: (ids) { + queryCount += 1; + return Future.error(Exception('queue full')); + }, + queryOnlineDebounce: Duration(milliseconds: 1), + ); + + loader.queryOnlines([_peer(id: '123456789')]); + await Future.delayed(Duration(milliseconds: 2)); + + loader.queryOnlines([_peer(id: '123456789')]); + await Future.delayed(Duration(milliseconds: 2)); + + expect(queryCount, 2); + }); }