From 242165b6ccc574a930b515d4ff55e7b64defd9b3 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 22 May 2026 09:54:50 +0300 Subject: Add widget test for double-tap download guard in PodcastEpisodesScreen Adds a Completer field to _FakeApiClient so tests can hold a download in-flight, and a new test that taps the download button twice while the first call is pending to verify _pendingDownloads suppresses the second API call. Co-Authored-By: Claude Sonnet 4.6 --- .../test/screens/podcast_episodes_screen_test.dart | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/player-android/test/screens/podcast_episodes_screen_test.dart b/player-android/test/screens/podcast_episodes_screen_test.dart index dcf289f..a027cc1 100644 --- a/player-android/test/screens/podcast_episodes_screen_test.dart +++ b/player-android/test/screens/podcast_episodes_screen_test.dart @@ -90,6 +90,11 @@ class _FakeApiClient extends PlayerApiClient { /// Records every call to [downloadEpisode] — useful for download tests. int downloadEpisodeCallCount = 0; + /// When non-null, [downloadEpisode] returns this completer's future instead + /// of resolving immediately. This lets a test hold the first call + /// in-flight so a second tap can be verified to be suppressed by the guard. + Completer? downloadCompleter; + @override Future> listEpisodes( int podcastSetId, { @@ -111,6 +116,8 @@ class _FakeApiClient extends PlayerApiClient { Future downloadEpisode(int episodeId) async { downloadEpisodeCallCount++; if (downloadError != null) throw downloadError!; + // If a completer is provided, the caller controls when the future resolves. + if (downloadCompleter != null) return downloadCompleter!.future; return downloadResult!; } } @@ -883,6 +890,38 @@ void main() { expect(find.byType(SnackBar), findsOneWidget); expect(find.textContaining('Could not reach'), findsOneWidget); }); + + testWidgets( + 'double-tap download button only fires one API call', + (tester) async { + // Hold the first download in-flight so the button is still showing when + // the second tap happens — this is the window the guard must close. + final fakeClient = _FakeApiClient() + ..episodesResult = [_kEpisode1] + ..downloadCompleter = Completer(); + + await _pumpScreen(tester, fakeClient); + await tester.pumpAndSettle(); + + final downloadButton = + find.byKey(const Key('episode_download_button_1')); + expect(downloadButton, findsOneWidget); + + // First tap: starts the download; future not yet resolved. + await tester.tap(downloadButton); + await tester.pump(); + + // Second tap: should be suppressed by the _pendingDownloads guard. + await tester.tap(downloadButton); + await tester.pump(); + + // Resolve the in-flight download to allow pumpAndSettle to complete. + fakeClient.downloadCompleter!.complete(_kDownloadedMedia); + await tester.pumpAndSettle(); + + // Despite two taps, the API must have been called exactly once. + expect(fakeClient.downloadEpisodeCallCount, equals(1)); + }); }); // -------------------------------------------------------------------------- -- cgit v1.2.3