Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ class _SmoothAutocompleteTextFieldState
return _SearchResults.empty();
}

final DateTime start = DateTime.now();

if (_suggestions[search] != null) {
return _suggestions[search]!;
} else if (widget.manager == null ||
Expand All @@ -222,19 +220,17 @@ class _SmoothAutocompleteTextFieldState
_suggestions[search] = _SearchResults(
await widget.manager!.getSuggestions(search),
);
} catch (_) {}
} catch (_) {
return _SearchResults.empty();
} finally {
_setLoading(false);
}
Comment thread
Sherley-Sonali marked this conversation as resolved.

if (_suggestions[search]?.isEmpty ?? true && search == _searchInput) {
_setLoading(false);
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_setLoading(false) is now executed unconditionally in finally. If multiple _getSuggestions calls overlap (common while typing), an older request finishing first will clear the spinner even though a newer request is still in-flight. Consider tying the loading state to the latest query (e.g., only clear when search == _searchInput, or use a monotonically increasing requestId/counter and only clear for the latest request).

Suggested change
_setLoading(false);
}
if (_suggestions[search]?.isEmpty ?? true && search == _searchInput) {
_setLoading(false);
}
if (search == _searchInput) {
_setLoading(false);
}
}

Copilot uses AI. Check for mistakes.
if (_searchInput != search &&
start.difference(DateTime.now()).inSeconds > 5) {
// Ignore this request, it's too long and this is not even the current search
return _SearchResults.empty();
} else {
return _suggestions[search] ?? _SearchResults.empty();
}
return _suggestions[search]!;
}
}

Expand Down
16 changes: 8 additions & 8 deletions packages/smooth_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -1052,18 +1052,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
matomo_tracker:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1679,10 +1679,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.9"
torch_light:
dependency: "direct main"
description:
Expand Down
66 changes: 66 additions & 0 deletions packages/smooth_app/test/pages/autocomplete_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:openfoodfacts/openfoodfacts.dart';

/// Mock autocompleter that simulates slow/failing network
class _MockAutocompleter implements Autocompleter {
_MockAutocompleter({this.delay = Duration.zero, this.shouldFail = false});

final Duration delay;
final bool shouldFail;
int callCount = 0;

@override
Future<List<String>> getSuggestions(String input) async {
callCount++;
await Future<void>.delayed(delay);
if (shouldFail) {
throw Exception('Network error');
}
return <String>['$input-result1', '$input-result2'];
}
}

void main() {
group('AutocompleteManager', () {
test('cache hit returns immediately without server call', () async {
final _MockAutocompleter mock = _MockAutocompleter();
final AutocompleteManager manager = AutocompleteManager(mock);

// First call hits server
final List<String> first = await manager.getSuggestions('bo');
expect(first, contains('bo-result1'));
expect(mock.callCount, 1);

// Second call should hit cache — no new server call
final List<String> second = await manager.getSuggestions('bo');
expect(second, contains('bo-result1'));
expect(mock.callCount, 1); // still 1 — cache served it
});

test('returns most recent cached result when out of order', () async {
final _MockAutocompleter slowMock = _MockAutocompleter(
delay: const Duration(milliseconds: 100),
);
final AutocompleteManager manager = AutocompleteManager(slowMock);

// Fire both requests simultaneously
final Future<List<String>> boFuture = manager.getSuggestions('bo');
final Future<List<String>> botFuture = manager.getSuggestions('bot');

final List<String> boResult = await boFuture;
final List<String> botResult = await botFuture;

// Both should have cached their own results
expect(boResult, isNotEmpty);
expect(botResult, isNotEmpty);
});
Comment on lines +40 to +56
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name/intent (“returns most recent cached result when out of order”) isn’t being asserted: both requests use the same delay and the expectations only check isNotEmpty. This can pass even if out-of-order/stale-result handling is broken. Either rename the test to what it actually verifies, or make it deterministic by forcing out-of-order completion (different delays) and asserting the manager returns/keeps the latest-query result.

Copilot uses AI. Check for mistakes.

test('network failure throws exception', () async {
final _MockAutocompleter failMock = _MockAutocompleter(shouldFail: true);
final AutocompleteManager manager = AutocompleteManager(failMock);

// Should throw — smooth-app catch block must handle this
expect(() => manager.getSuggestions('bo'), throwsException);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This async error assertion is likely incorrect: manager.getSuggestions returns a Future, so wrapping it in () => ... won’t throw synchronously. Use an async-aware expectation (e.g., expect(manager.getSuggestions('bo'), throwsA(...)), or await expectLater(...)) so the test actually verifies the failure mode.

Suggested change
expect(() => manager.getSuggestions('bo'), throwsException);
await expectLater(manager.getSuggestions('bo'), throwsException);

Copilot uses AI. Check for mistakes.
});
});
}
Loading