import 'package:audio_service/audio_service.dart'; import 'package:drift/drift.dart' show 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: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/mini_player.dart'; import 'package:melo/player/now_playing_screen.dart'; import 'package:melo/player/player_expansion_controller.dart'; import 'package:melo/settings/app_settings.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Ersetzt in [MeloAudioHandler.loadPlaylist] den echten just_audio-Zugriff — /// dieselbe Klasse wie in now_playing_heart_test.dart. 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() { 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(() => SharedPreferences.setMockInitialValues({})); testWidgets( 'NowPlayingScreen in einem während der Öffnen-Geste noch kleinen, ' 'interpolierten Rechteck (progress ~0.4) wirft keinen ' 'RenderFlex-Overflow', (tester) async { TestWidgetsFlutterBinding.ensureInitialized(); driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; stelleKanaeleRuhig(); final db = MeloDb(NativeDatabase.memory()); final kategorien = CategoryService(db); final playlists = PlaylistService(db); final einstellungen = AppSettings(); await einstellungen.init(); final handler = _FakeAudioHandler(db); addTearDown(kategorien.dispose); addTearDown(playlists.dispose); // Ein echtes MediaItem, wie es beim Abspielen im mediaItem-Stream steht // — NowPlayingScreen zeigt sonst nur "Nichts in Wiedergabe" und hat dann // gar keinen Inhalt, der überlaufen könnte. handler.mediaItem.add(const MediaItem( id: 'song-1', title: 'Roses', artist: 'Neoni', )); // Dieselbe Rect-Interpolation wie HomeShell._miniRect/_vollbildRect bei // progress=0.4 auf der Standard-Testbildschirmgröße (800x600, siehe // flutter_test-Default) — main.dart positioniert NowPlayingScreen // während der Öffnen-Geste über genau so ein Positioned.fromRect mit // Rect.lerp zwischen Mini- und Vollbild-Rechteck. Bei p>0.3 blendet der // Inhalt laut Spec schon sichtbar ein (main.dart: Opacity((p-0.3)/0.7)), // das Rechteck ist zu dem Zeitpunkt aber noch weit von der // Vollbildgröße entfernt. const p = 0.4; const screenSize = Size(800, 600); const miniHoehe = MiniPlayer.hoehe; final unten = screenSize.height - miniHoehe - kBottomNavigationBarHeight; final miniRect = Rect.fromLTWH(0, unten, screenSize.width, miniHoehe); final vollbildRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height); final rect = Rect.lerp(miniRect, vollbildRect, p)!; 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: Stack( children: [ Positioned.fromRect( rect: rect, child: const NowPlayingScreen(), ), ], ), ), ), ), ); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); // Aufräum-Reihenfolge wie in den übrigen Tests dieser Datei-Familie: // erst den Baum abbauen, dann den Handler disposen (offener Positions- // Timer, siehe now_playing_heart_test.dart). await tester.pumpWidget(const SizedBox()); handler.dispose(); await tester.pump(const Duration(milliseconds: 1)); }); }