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/player/audio_handler.dart'; import 'package:melo/player/player_expansion_controller.dart'; import 'package:melo/playlists/playlist_detail_screen.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; late String playlistId; 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, )); playlistId = await db.createPlaylist('Road Trip'); await db.addSongToPlaylist(playlistId, 'song-1', 0); }); tearDown(() { handler.dispose(); kategorien.dispose(); playlists.dispose(); }); testWidgets( 'Song in einer Wiedergabeliste antippen öffnet den Vollbild-Player', (tester) async { final playlist = Playlist( id: playlistId, name: 'Road Trip', createdAtMs: 0, updatedAtMs: 0, deleted: false, ); 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: PlaylistDetailScreen(playlist: playlist), ), ), ); await tester.pumpAndSettle(); 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 beim Abbau des // Baums als offener Fake-Timer stehen. await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(milliseconds: 1)); }); }