Auf Wunsch von Dustin: seine beste Freundin sortiert ihre Sammlung ueber das Album-Feld. In Melo ist der Album-Titel deshalb ab jetzt die Kategorie — sie muss nichts neu machen. 1) Album = Kategorie - kategorienAusTags(): Album-Titel steht VORN in der Kategorienliste (die erste Kategorie bestimmt das Coverbild), Genres dahinter. - Beide Scans (android_scan + scan_service) tragen ihn ein; von Hand gepflegte Kategorien (categoriesEdited) bleiben unberuehrt. - DB-Schema 9: einmalige Nachruestung bestehender Bibliotheken, damit das nicht erst beim naechsten vollstaendigen Scan sichtbar wird (der auf Android nur laeuft, wenn sich die Dateianzahl aendert). 2) Reiter: 'Songs/Kuenstler/Alben' -> 'Lieder/Kategorie/Kuenstler' - Neu: library/category_list.dart mit groupByCategory(); Lieder ohne Kategorie sammeln sich am Ende unter "Ohne Kategorie". - Entfernt: library/album_list.dart, groupByAlbum(), albumArtistLabel() — mit dem Alben-Reiter tot geworden. Das Album-FELD bleibt erhalten. 3) YouTube-Downloads ohne Original-Album - Feld "Kategorie (optional)" im YouTube-Bereich, mit Vorschlaegen aus der Bibliothek und freier Eingabe. - ordneDownloadEin() verwirft nach dem Scan das Album-Tag (yt-dlp leitet es aus Kanal/Playlist ab — als Kategorie waere das Unsinn) und setzt stattdessen die gewaehlte Kategorie. Beides als "von Hand gesetzt" markiert, damit der naechste Scan es nicht zurueckholt. - MeloDb.songByPath() und MeloDb.verwirfAlbum() neu. - CategoryService.alleNamen: Kategorienamen ohne zusaetzliche Abfrage — ohne das flackerte die Vorschlagsliste und im Widget-Test blieb ein Aufraeum-Timer von drift haengen. 295 Tests gruen (19 neue, 1 uebersprungen), flutter analyze ohne Befund. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
147 lines
5.0 KiB
Dart
147 lines
5.0 KiB
Dart
import 'package:drift/drift.dart';
|
|
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/my_music_screen.dart';
|
|
import 'package:melo/library/playlist_service.dart';
|
|
import 'package:melo/player/audio_handler.dart';
|
|
import 'package:melo/settings/app_settings.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
Widget _wrap(MeloDb db, LibraryService lib, PlaylistService playlists,
|
|
MeloAudioHandler handler, Widget child) {
|
|
return 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()),
|
|
],
|
|
child: MaterialApp(home: Scaffold(body: child)),
|
|
);
|
|
}
|
|
|
|
Future<void> _insert(
|
|
MeloDb db, {
|
|
required String id,
|
|
required String title,
|
|
String? artist,
|
|
String? album,
|
|
}) {
|
|
return db.into(db.songs).insert(SongsCompanion.insert(
|
|
id: id,
|
|
path: '/$id.mp3',
|
|
title: title,
|
|
artist: Value(artist),
|
|
album: Value(album),
|
|
dateAddedMs: 0,
|
|
updatedAtMs: 0,
|
|
));
|
|
}
|
|
|
|
void main() {
|
|
setUp(() => SharedPreferences.setMockInitialValues({}));
|
|
|
|
testWidgets('Meine Musik hat die Unterreiter Lieder, Kategorie und Künstler',
|
|
(tester) async {
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
final lib = LibraryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final handler = MeloAudioHandler(db: db);
|
|
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
|
|
|
await tester.pumpWidget(
|
|
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Lieder'), findsOneWidget);
|
|
expect(find.text('Kategorie'), findsOneWidget);
|
|
expect(find.text('Künstler'), findsOneWidget);
|
|
// Der Alben-Reiter ist im Kategorie-Reiter aufgegangen.
|
|
expect(find.text('Alben'), findsNothing);
|
|
|
|
handler.dispose();
|
|
await db.close();
|
|
});
|
|
|
|
testWidgets('Unterreiter Künstler zeigt die Künstlerliste statt der Songs',
|
|
(tester) async {
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
final lib = LibraryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final handler = MeloAudioHandler(db: db);
|
|
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
|
|
|
await tester.pumpWidget(
|
|
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Künstler'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Neoni'), findsOneWidget);
|
|
// Unterreiter wechseln den Inhalt, sie öffnen keinen neuen Bildschirm —
|
|
// Suchfeld und Reiter bleiben sichtbar.
|
|
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
|
|
expect(find.text('Lieder'), findsOneWidget);
|
|
expect(find.text('Shuffle-Wiedergabe'), findsNothing);
|
|
|
|
handler.dispose();
|
|
await db.close();
|
|
});
|
|
|
|
testWidgets('Unterreiter Kategorie zeigt den Album-Titel als Kategorie',
|
|
(tester) async {
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
final lib = LibraryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final handler = MeloAudioHandler(db: db);
|
|
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
|
|
|
await tester.pumpWidget(
|
|
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Das Album ist die Kategorie (siehe kategorienAusTags) — der Titel
|
|
// taucht deshalb unter "Roses" auf.
|
|
await db.setCategories('1', ['Roses']);
|
|
await tester.tap(find.text('Kategorie'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Roses'), findsOneWidget);
|
|
expect(find.text('1 Lieder'), findsOneWidget);
|
|
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
|
|
expect(find.text('Lieder'), findsOneWidget);
|
|
|
|
handler.dispose();
|
|
await db.close();
|
|
});
|
|
|
|
testWidgets('Titel ohne Kategorie sammeln sich unter "Ohne Kategorie"',
|
|
(tester) async {
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
final lib = LibraryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final handler = MeloAudioHandler(db: db);
|
|
await _insert(db, id: '1', title: 'Song A');
|
|
|
|
await tester.pumpWidget(
|
|
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Kategorie'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Ohne Kategorie'), findsOneWidget);
|
|
|
|
handler.dispose();
|
|
await db.close();
|
|
});
|
|
}
|