153 lines
6.6 KiB
Dart
153 lines
6.6 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_test/flutter_test.dart';
|
|
import 'package:melo/library/category_service.dart';
|
|
import 'package:melo/library/database.dart';
|
|
import 'package:melo/library/library_service.dart';
|
|
import 'package:melo/library/playlist_service.dart';
|
|
import 'package:melo/main.dart';
|
|
import 'package:melo/player/audio_handler.dart';
|
|
import 'package:melo/player/mini_player.dart';
|
|
import 'package:melo/player/player_expansion_controller.dart';
|
|
import 'package:melo/playlists/playlist_detail_screen.dart';
|
|
import 'package:melo/services/baka_auth.dart';
|
|
import 'package:melo/services/download_service.dart';
|
|
import 'package:melo/services/gast_zugang.dart';
|
|
import 'package:melo/services/melo_cloud_service.dart';
|
|
import 'package:melo/services/navidrome_service.dart';
|
|
import 'package:melo/services/offline_mode.dart';
|
|
import 'package:melo/services/sync_service.dart';
|
|
import 'package:melo/services/yt_download_service.dart';
|
|
import 'package:melo/services/yt_search_service.dart';
|
|
import 'package:melo/settings/app_settings.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class _MemorySpeicher implements TokenSpeicher {
|
|
final Map<String, String> werte = {};
|
|
@override
|
|
Future<String?> lesen(String key) async => werte[key];
|
|
@override
|
|
Future<void> schreiben(String key, String wert) async => werte[key] = wert;
|
|
@override
|
|
Future<void> loeschen(String key) async => werte.remove(key);
|
|
}
|
|
|
|
/// Ersetzt in [MeloAudioHandler.loadPlaylist] den echten just_audio-Zugriff —
|
|
/// dieselbe Klasse wie in playlist_detail_navigation_test.dart.
|
|
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() {
|
|
setUp(() => SharedPreferences.setMockInitialValues({}));
|
|
|
|
testWidgets(
|
|
'Per Navigator.push geöffneter PlaylistDetailScreen (wie aus '
|
|
'playlists_screen.dart:120) sieht PlayerExpansionController — echtes '
|
|
'HomeShell, echter Navigator.push, kein direkt daneben geklebter '
|
|
'Test-Provider (Review-Fund: Provider war bisher nur INNERHALB von '
|
|
'HomeShell.build() bereitgestellt, eine gepushte Route ist im Baum '
|
|
'aber ein Geschwister von HomeShell, kein Nachfahre)', (tester) async {
|
|
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
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,
|
|
));
|
|
final playlistId = await db.createPlaylist('Road Trip');
|
|
await db.addSongToPlaylist(playlistId, 'song-1', 0);
|
|
final playlist = Playlist(
|
|
id: playlistId,
|
|
name: 'Road Trip',
|
|
createdAtMs: 0,
|
|
updatedAtMs: 0,
|
|
deleted: false,
|
|
);
|
|
|
|
final lib = LibraryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final handler = _FakeAudioHandler(db);
|
|
final auth = BakaAuth(speicher: _MemorySpeicher());
|
|
final expansion = PlayerExpansionController(vsync: tester);
|
|
addTearDown(expansion.dispose);
|
|
|
|
// Dieselbe Provider-Verdrahtung wie home_shell_real_expansion_test.dart,
|
|
// PlayerExpansionController aber jetzt OBERHALB von MaterialApp/HomeShell
|
|
// — genau wie MeloApps MultiProvider nach dem Fix (main.dart), nicht mehr
|
|
// nur INNERHALB von HomeShell.build() bereitgestellt.
|
|
await tester.pumpWidget(MultiProvider(
|
|
providers: [
|
|
Provider<MeloDb>.value(value: db),
|
|
ChangeNotifierProvider<LibraryService>.value(value: lib),
|
|
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
|
|
Provider<MeloAudioHandler>.value(value: handler),
|
|
ChangeNotifierProvider<CategoryService>(create: (_) => CategoryService(db)),
|
|
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
|
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
|
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
|
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
|
ChangeNotifierProvider<YtDownloadService>(
|
|
create: (_) => YtDownloadService(auth: auth)),
|
|
ChangeNotifierProvider<YtSearchService>(
|
|
create: (_) => YtSearchService(auth: auth)),
|
|
ChangeNotifierProvider<DownloadService>(
|
|
create: (_) => DownloadService(db: db, navidrome: NavidromeService())),
|
|
ChangeNotifierProvider<SyncService>(
|
|
create: (_) =>
|
|
SyncService(db: db, cloud: MeloCloudService(auth: auth))),
|
|
ChangeNotifierProvider<PlayerExpansionController>.value(
|
|
value: expansion),
|
|
],
|
|
child: const MaterialApp(home: HomeShell()),
|
|
));
|
|
await tester.pump();
|
|
|
|
// Navigator.push GENAU wie playlists_screen.dart:120 — von einem Kontext
|
|
// INNERHALB des echten HomeShell-Baums aus (MiniPlayer steckt immer im
|
|
// Stack, auch ohne laufenden Titel), nicht von einem manuell daneben
|
|
// gebauten Test-Baum.
|
|
final homeShellContext = tester.element(find.byType(MiniPlayer));
|
|
Navigator.of(homeShellContext).push(MaterialPageRoute(
|
|
builder: (_) => PlaylistDetailScreen(playlist: playlist),
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Roses'), findsOneWidget);
|
|
|
|
// Tap wie ein echter Nutzer — löst intern playSongs() und danach
|
|
// context.read<PlayerExpansionController>().open(context) aus.
|
|
await tester.tap(find.text('Roses'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Bug-Symptom: die ProviderNotFoundException landet im try/catch von
|
|
// PlaylistDetailScreen und zeigt eine "Wiedergabe fehlgeschlagen"-
|
|
// Snackbar, statt dass der Player öffnet.
|
|
expect(find.textContaining('Wiedergabe fehlgeschlagen'), findsNothing,
|
|
reason: 'context.read<PlayerExpansionController>() darf hier keine '
|
|
'ProviderNotFoundException werfen.');
|
|
// Beweis, dass .open() tatsächlich denselben (einzigen) Controller
|
|
// erreicht hat, nicht nur, dass keine Exception flog.
|
|
expect(expansion.progress, 1.0);
|
|
|
|
// Aufräum-Reihenfolge wie home_shell_real_expansion_test.dart: erst den
|
|
// Baum abbauen, dann den Handler disposen (offener Positions-Timer).
|
|
await tester.pumpWidget(const SizedBox());
|
|
handler.dispose();
|
|
await tester.pump(const Duration(milliseconds: 1));
|
|
});
|
|
}
|