Files
Melo/test/player/now_playing_heart_test.dart
T
Hermes (Server) aea80d9755 3 Tap-Stellen öffnen den Vollbild-Player jetzt über PlayerExpansionController statt Navigator.push
Zusätzlich test/player/now_playing_heart_test.dart angepasst (Plan-Lücke:
nutzt denselben SongZeile-Tap-Pfad, war nicht in Task 3s Datei-Liste
erwähnt) — Provider ergänzt, Route für die Layout-Prüfung selbst separat
geöffnet, bis Task 5 NowPlayingScreen zu Overlay-Inhalt umbaut.
2026-08-29 14:34:30 +02:00

177 lines
6.1 KiB
Dart

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/now_playing_screen.dart';
import 'package:melo/player/player_expansion_controller.dart';
import 'package:melo/settings/app_settings.dart';
import 'package:melo/shared/favorite_button.dart';
/// Ersetzt in [MeloAudioHandler.loadPlaylist] den echten just_audio-Zugriff —
/// dieselbe Klasse wie in song_list_navigation_test.dart, dort erprobt.
class _FakeAudioHandler extends MeloAudioHandler {
_FakeAudioHandler(MeloDb db) : super(db: db);
@override
Future<int> loadPlaylist(List<MediaItem> 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,
);
// Öffnet den Vollbild-Player über denselben Weg wie in
// song_list_navigation_test.dart (Tap auf eine SongZeile) — dieser Weg ist
// dort nachweislich unproblematisch, im Unterschied zu einem direkten
// Aufbau von NowPlayingScreen mit vorab befülltem MediaItem-Stream.
//
// Seit dem Umbau auf PlayerExpansionController (Plan
// 2026-08-29-blur-oeffnen-transition, Task 3) zeigt der Tap allein noch
// keine sichtbare Oberfläche — das Overlay-Rendering kommt erst mit
// Task 4/5 ("Bekannte Zwischenlücke", siehe Plan). Für die Prüfung von
// NowPlayingScreens eigenem Layout (Herz-Position) wird die Route hier
// deshalb zusätzlich, unabhängig vom Produktions-Pfad, direkt geöffnet.
// Sobald Task 5 NowPlayingScreen zu Overlay-Inhalt umbaut, muss dieser
// Test ohnehin überarbeitet werden (Task 5 durchsucht dafür selbst
// `test -iname "*now_playing*"`, was diese Datei einschließt).
Future<void> oeffnePlayer(WidgetTester tester) async {
final expansion = PlayerExpansionController(vsync: tester);
addTearDown(expansion.dispose);
await tester.pumpWidget(
MultiProvider(
providers: [
Provider<MeloDb>.value(value: db),
Provider<MeloAudioHandler>.value(value: handler),
ChangeNotifierProvider<CategoryService>.value(value: kategorien),
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
ChangeNotifierProvider<AppSettings>.value(value: einstellungen),
ChangeNotifierProvider<PlayerExpansionController>.value(
value: expansion),
],
child: MaterialApp(
home: Scaffold(
body: SongZeile(
song: derSong(),
warteschlange: [derSong()],
index: 0,
),
),
),
),
);
await tester.tap(find.text('Roses'));
await tester.pumpAndSettle();
expect(expansion.progress, 1.0);
Navigator.of(tester.element(find.byType(SongZeile))).push(
MaterialPageRoute(builder: (_) => const NowPlayingScreen()),
);
await tester.pumpAndSettle();
}
Future<void> raeumeAuf(WidgetTester tester) async {
// 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_list_navigation_test.dart.
await tester.pumpWidget(const SizedBox());
await tester.pump(const Duration(milliseconds: 1));
}
testWidgets(
'Herz sitzt neben dem Songnamen, nicht mehr in der AppBar',
(tester) async {
await oeffnePlayer(tester);
expect(find.byType(NowPlayingScreen), findsOneWidget);
// Nicht mehr in der AppBar:
final herzInAppBar = find.descendant(
of: find.byType(AppBar),
matching: find.byType(FavoriteButton),
);
expect(herzInAppBar, findsNothing);
// Aber sichtbar auf dem Bildschirm, in derselben Row wie der Titel.
final titelRow = find
.ancestor(of: find.text('Roses'), matching: find.byType(Row))
.first;
final herzBeimTitel = find.descendant(
of: titelRow,
matching: find.byType(FavoriteButton),
);
expect(herzBeimTitel, findsOneWidget);
await raeumeAuf(tester);
});
}