fix(search): reject pending completions after clearing results

This commit is contained in:
zarzet committed 2026-10-05 17:53:19 +07:00
1 parent bf40ef8ebe
commit cfeeaa3084
2 files changed
+70 -1

No files matched your search

+6 -1
View File
@@ -161,7 +161,8 @@ class TrackNotifier extends Notifier<TrackState> {
return const TrackState();
}
bool _isRequestValid(int requestId) => requestId == _currentRequestId;
bool _isRequestValid(int requestId) =>
ref.mounted && requestId == _currentRequestId;
Future<void> fetchFromUrl(String url) async {
final requestId = ++_currentRequestId;
@@ -170,6 +171,7 @@ class TrackNotifier extends Notifier<TrackState> {
try {
var extensionHandler = await PlatformBridge.findURLHandler(url);
if (!_isRequestValid(requestId)) return;
if (extensionHandler == null) {
final extensionState = ref.read(extensionProvider);
if (!extensionState.isInitialized && extensionState.isLoading) {
@@ -181,6 +183,7 @@ class TrackNotifier extends Notifier<TrackState> {
.waitForInitialization(timeout: _extensionInitRetryTimeout);
if (!_isRequestValid(requestId)) return;
extensionHandler = await PlatformBridge.findURLHandler(url);
if (!_isRequestValid(requestId)) return;
}
}
@@ -217,6 +220,7 @@ class TrackNotifier extends Notifier<TrackState> {
if (attempt < 3) {
await Future<void>.delayed(const Duration(milliseconds: 500));
if (!_isRequestValid(requestId)) return;
}
}
@@ -547,6 +551,7 @@ class TrackNotifier extends Notifier<TrackState> {
}
void clear() {
_currentRequestId++;
state = const TrackState();
}
+64
View File
@@ -62,4 +62,68 @@ void main() {
expect(container.read(trackProvider).isLoading, isFalse);
},
);
for (final fails in [false, true]) {
test(
'clear rejects a pending search ${fails ? 'error' : 'result'}',
() async {
const channel = MethodChannel('com.zarz.spotiflac/backend');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
final response = Completer<String>();
final started = Completer<void>();
messenger.setMockMethodCallHandler(channel, (call) async {
if (call.method != 'customSearchWithExtension') return null;
started.complete();
return response.future;
});
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
final container = ProviderContainer();
addTearDown(container.dispose);
final notifier = container.read(trackProvider.notifier);
final search = notifier.customSearch('clear-search-$fails', 'query');
await started.future;
notifier.clear();
if (fails) {
response.completeError(PlatformException(code: 'late-search-error'));
} else {
response.complete('[{"id":"late","name":"Late result"}]');
}
await search;
final state = container.read(trackProvider);
expect(state.hasContent, isFalse);
expect(state.isLoading, isFalse);
expect(state.error, isNull);
},
);
}
for (final handler in [null, 'clear-url-provider']) {
test('clear rejects URL preflight completion ($handler)', () async {
const channel = MethodChannel('com.zarz.spotiflac/backend');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
final response = Completer<String?>();
final started = Completer<void>();
messenger.setMockMethodCallHandler(channel, (call) async {
expect(call.method, 'findURLHandler');
started.complete();
return response.future;
});
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
final container = ProviderContainer();
addTearDown(container.dispose);
final notifier = container.read(trackProvider.notifier);
final fetch = notifier.fetchFromUrl(
'https://example.test/clear-$handler',
);
await started.future;
notifier.clear();
response.complete(handler);
await fetch;
expect(container.read(trackProvider).hasContent, isFalse);
expect(container.read(trackProvider).isLoading, isFalse);
expect(container.read(trackProvider).error, isNull);
});
}
}