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: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/search_screen.dart'; import 'package:melo/settings/app_settings.dart'; /// Die Suche zeigte früher eine flache Liederliste. Jetzt trennt sie die /// Treffer nach Art — dieser Test prüft, dass die Abschnitte erscheinen und /// nur die passenden Treffer enthalten. void main() { late MeloDb db; late CategoryService kategorien; late AppSettings einstellungen; late PlaylistService playlists; setUp(() async { // Jeder Test legt eine eigene In-Memory-Datenbank an; die Warnung über // mehrere Instanzen ist hier erwartet und kein Befund. driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; SharedPreferences.setMockInitialValues({}); db = MeloDb(NativeDatabase.memory()); kategorien = CategoryService(db); playlists = PlaylistService(db); einstellungen = AppSettings(); await einstellungen.init(); Future song(String id, String titel, String kuenstler) => db.into(db.songs).insert(SongsCompanion.insert( id: id, path: '/music/$id.mp3', title: titel, artist: Value(kuenstler), dateAddedMs: 0, updatedAtMs: 0, )); await song('s1', 'Roses', 'Neoni'); await song('s2', 'Demons', 'Nightcore Gaming'); await song('s3', 'Wild Roses', 'Lori'); await db.setCategories('s1', ['Trap cool']); await db.createPlaylist('Rosengarten'); }); // Kein db.close(): in Widget-Tests plant drift dabei eine Aufräum-Aufgabe // ein, die der Testrahmen nicht mehr abarbeitet (siehe BACKLOG.md). Die // Dienste liegen als `.value` im Baum, werden also nicht von ihm entsorgt. tearDown(() { kategorien.dispose(); playlists.dispose(); }); Future pumpe(WidgetTester tester) async { await tester.pumpWidget( MultiProvider( providers: [ Provider.value(value: db), ChangeNotifierProvider.value(value: kategorien), ChangeNotifierProvider.value(value: einstellungen), ChangeNotifierProvider.value(value: playlists), ], child: const MaterialApp(home: Scaffold(body: SearchScreen())), ), ); await tester.pumpAndSettle(); } Future tippe(WidgetTester tester, String text) async { await tester.enterText(find.byType(TextField), text); // Die Eingabe ist entprellt — ohne dieses Warten passiert nichts. await tester.pump(const Duration(milliseconds: 300)); await tester.pumpAndSettle(); } Future aufraeumen(WidgetTester tester) async { await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(milliseconds: 1)); } testWidgets('leeres Feld zeigt den Hinweis statt Treffer', (tester) async { await pumpe(tester); expect(find.text('Suchbegriff eingeben'), findsOneWidget); expect(find.text('TITEL 2'), findsNothing); await aufraeumen(tester); }); testWidgets('trennt Treffer nach Titel, Künstler und Listen', (tester) async { await pumpe(tester); await tippe(tester, 'rose'); expect(find.text('TITEL 2'), findsOneWidget); expect(find.text('WIEDERGABELISTEN 1'), findsOneWidget); expect(find.text('Roses'), findsOneWidget); expect(find.text('Wild Roses'), findsOneWidget); expect(find.text('Rosengarten'), findsOneWidget); // "Demons" passt nicht und darf nicht auftauchen. expect(find.text('Demons'), findsNothing); await aufraeumen(tester); }); testWidgets('findet Künstler als eigenen Abschnitt', (tester) async { await pumpe(tester); await tippe(tester, 'nightcore'); expect(find.text('KÜNSTLER 1'), findsOneWidget); expect(find.text('Nightcore Gaming'), findsWidgets); await aufraeumen(tester); }); testWidgets('findet Kategorien als eigenen Abschnitt', (tester) async { await pumpe(tester); await tippe(tester, 'trap'); expect(find.text('KATEGORIEN 1'), findsOneWidget); expect(find.text('Trap cool'), findsWidgets); await aufraeumen(tester); }); testWidgets('ohne Treffer erscheint "Nichts gefunden"', (tester) async { await pumpe(tester); await tippe(tester, 'jazzfrühstück'); expect(find.text('Nichts gefunden'), findsOneWidget); await aufraeumen(tester); }); testWidgets('entprellt: direkt nach dem Tippen steht noch nichts da', (tester) async { await pumpe(tester); await tester.enterText(find.byType(TextField), 'rose'); // Weniger als die Entprellzeit — die Treffer dürfen noch nicht stehen. await tester.pump(const Duration(milliseconds: 100)); expect(find.text('TITEL 2'), findsNothing); await tester.pump(const Duration(milliseconds: 300)); await tester.pumpAndSettle(); expect(find.text('TITEL 2'), findsOneWidget); await aufraeumen(tester); }); testWidgets('abgeschickte Suche steht danach im Verlauf', (tester) async { await pumpe(tester); await tippe(tester, 'rose'); await tester.testTextInput.receiveAction(TextInputAction.search); await tester.pumpAndSettle(); // Feld leeren — dann muss der Verlauf sichtbar werden. await tester.tap(find.byTooltip('Eingabe löschen')); await tester.pumpAndSettle(); expect(find.text('ZULETZT GESUCHT'), findsOneWidget); expect(find.text('rose'), findsOneWidget); await aufraeumen(tester); }); }