import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:melo/library/database.dart'; import 'package:melo/downloads/server_titel_screen.dart'; import 'package:melo/player/audio_handler.dart'; import 'package:melo/services/download_service.dart'; import 'package:melo/services/navidrome_service.dart'; /// Merkt sich nur, was verlangt wurde — echte Downloads brauchen einen /// Server, und darum geht es hier nicht. class _FakeDownloads extends DownloadService { _FakeDownloads(MeloDb db) : super(db: db, navidrome: NavidromeService()); final geladen = []; final entfernt = []; @override bool get laeuft => false; @override Future ladeEinzelnenTitel(SubsonicSong song) async { geladen.add(song.id); return true; } @override Future entferne(String navidromeId) async { entfernt.add(navidromeId); return true; } } void main() { final titel = [ const SubsonicSong( id: 'nav-1', titel: 'Nachtpuls', kuenstler: 'Rotklang', album: 'Nacht', dauerSekunden: 200, ), ]; Future<({_FakeDownloads dienst, MeloAudioHandler handler})> pumpe( WidgetTester tester, {bool schonGeladen = false}) async { final db = MeloDb(NativeDatabase.memory()); addTearDown(db.close); if (schonGeladen) { // Der Bildschirm liest den Zustand einmal per db.downloadIds(). await db.merkeDownload(DownloadsCompanion.insert( navidromeId: 'nav-1', titel: 'Nachtpuls', groesseBytes: 1, geladenAmMs: 0, )); } final dienst = _FakeDownloads(db); // MeloAudioHandler startet einen periodischen Timer (Positions- // Buchführung). Ohne handler.dispose() meldet der Testrahmen ihn als // „pending" — dasselbe etablierte Muster wie in // test/library/my_music_screen_test.dart: handler.dispose() explizit am // Testende aufrufen (addTearDown greift zu spät, außerhalb der // FakeAsync-Zone, in der der Timer geprüft wird). final handler = MeloAudioHandler(db: db); await tester.pumpWidget( MultiProvider( providers: [ Provider.value(value: db), ChangeNotifierProvider.value(value: dienst), Provider.value(value: handler), ], child: MaterialApp( home: ServerTitelScreen( titel: 'Nacht', navidrome: NavidromeService(), holeTitel: () async => titel, ), ), ), ); await tester.pumpAndSettle( const Duration(milliseconds: 100), EnginePhase.sendSemanticsUpdate, const Duration(seconds: 5), ); return (dienst: dienst, handler: handler); } testWidgets('ein einzelner Titel lässt sich offline nehmen', (tester) async { final (:dienst, :handler) = await pumpe(tester); await tester.tap(find.byTooltip('Offline nehmen')); await tester.pumpAndSettle( const Duration(milliseconds: 100), EnginePhase.sendSemanticsUpdate, const Duration(seconds: 5), ); expect(dienst.geladen, ['nav-1']); expect(dienst.entfernt, isEmpty); handler.dispose(); }); testWidgets('ein schon geladener Titel bietet den Gegenweg an', (tester) async { final (:dienst, :handler) = await pumpe(tester, schonGeladen: true); // Zustand „schon offline": statt „Offline nehmen" steht dort das // Entfernen — ohne diesen Test wäre der halbe Knopf ungeprüft. expect(find.byTooltip('Offline nehmen'), findsNothing); await tester.tap(find.byTooltip('Vom Gerät entfernen')); await tester.pumpAndSettle( const Duration(milliseconds: 100), EnginePhase.sendSemanticsUpdate, const Duration(seconds: 5), ); expect(dienst.entfernt, ['nav-1']); expect(dienst.geladen, isEmpty); handler.dispose(); }); }