import 'package:audio_service/audio_service.dart'; import 'package:drift/drift.dart' show Value, driftRuntimeOptions; import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:melo/library/category_service.dart'; import 'package:melo/library/database.dart'; import 'package:melo/library/playlist_service.dart'; import 'package:melo/library/song_list.dart'; import 'package:melo/player/audio_handler.dart'; import 'package:melo/player/player_expansion_controller.dart'; import 'package:melo/settings/app_settings.dart'; /// Ersetzt in [MeloAudioHandler.loadPlaylist] den echten just_audio-Zugriff: /// der spricht über Plattform-Kanäle, die im Widget-Test nicht existieren /// und mit `MissingPluginException` scheitern würden. Alles andere /// (Warteschlange, Mediendaten) bleibt echtes Handler-Verhalten. class _FakeAudioHandler extends MeloAudioHandler { _FakeAudioHandler(MeloDb db) : super(db: db); @override Future loadPlaylist(List items, {int startIndex = 0}) async { queue.add(items); if (items.isNotEmpty) mediaItem.add(items[startIndex]); return 0; } } void main() { late MeloDb db; late CategoryService kategorien; late PlaylistService playlists; late AppSettings einstellungen; late _FakeAudioHandler handler; void stelleKanaeleRuhig() { final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; messenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/path_provider'), (call) async => '/tmp/melo-test', ); messenger.setMockMethodCallHandler( const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), (call) async => null, ); } setUp(() async { TestWidgetsFlutterBinding.ensureInitialized(); driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; SharedPreferences.setMockInitialValues({}); stelleKanaeleRuhig(); db = MeloDb(NativeDatabase.memory()); kategorien = CategoryService(db); playlists = PlaylistService(db); einstellungen = AppSettings(); await einstellungen.init(); handler = _FakeAudioHandler(db); await db.into(db.songs).insert(SongsCompanion.insert( id: 'song-1', path: '/music/roses.mp3', title: 'Roses', artist: const Value('Neoni'), dateAddedMs: 0, updatedAtMs: 0, )); }); tearDown(() { handler.dispose(); kategorien.dispose(); playlists.dispose(); }); Song derSong() => Song( id: 'song-1', path: '/music/roses.mp3', title: 'Roses', artist: 'Neoni', dateAddedMs: 0, updatedAtMs: 0, deleted: false, playCount: 0, categoriesEdited: false, metadataEdited: false, ); testWidgets('Song antippen öffnet den Vollbild-Player mit großem Cover', (tester) async { final expansion = PlayerExpansionController(vsync: tester); addTearDown(expansion.dispose); await tester.pumpWidget( MultiProvider( providers: [ Provider.value(value: db), Provider.value(value: handler), ChangeNotifierProvider.value(value: kategorien), ChangeNotifierProvider.value(value: playlists), ChangeNotifierProvider.value(value: einstellungen), ChangeNotifierProvider.value( value: expansion), ], child: MaterialApp( home: Scaffold( body: SongZeile( song: derSong(), warteschlange: [derSong()], index: 0, ), ), ), ), ); expect(expansion.progress, 0.0); await tester.tap(find.text('Roses')); await tester.pumpAndSettle(); expect(expansion.progress, 1.0); // Ohne diesen Abschluss bleibt ein drift-Stream-Abo (Favoriten-Herz) // beim Abbau des Baums als offener Fake-Timer stehen — dieselbe // Aufräum-Zeile wie in song_zeile_lauf_test.dart. await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(milliseconds: 1)); }); }