import 'package:flutter_test/flutter_test.dart'; import 'package:melo_app/models/song.dart'; import 'package:melo_app/services/player_service.dart'; Song _song(int id, String titel) => Song( id: id, titel: titel, kuenstler: 'Künstler $id', dauerSekunden: 100, dateiPfad: '', ); void main() { // PlayerService ist ein Singleton — Zustand vor jedem Test zurücksetzen setUp(() { final p = PlayerService(); p.setZufallsmodus(false); p.setWiederholmodus(Wiederholmodus.aus); p.cancelSleepTimer(); p.setWarteschlange([]); }); group('Warteschlange', () { test('setWarteschlange + Getter', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]); expect(p.warteschlange.length, 3); expect(p.aktuellerIndex, 0); expect(p.aktuellerSong?.titel, 'A'); }); test('warteschlange ist eine unveränderliche Kopie', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B')]); expect(() => p.warteschlange.add(_song(3, 'C')), throwsUnsupportedError); expect(p.warteschlange.length, 2); }); test('spieleAlsNaechstes fügt nach dem aktuellen Song ein', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]); p.spieleAlsNaechstes(_song(9, 'X')); expect(p.warteschlange.map((s) => s.id).toList(), [1, 9, 2, 3]); expect(p.aktuellerIndex, 0); }); test('spieleAlsNaechstes ohne Warteschlange hängt an', () { final p = PlayerService(); p.spieleAlsNaechstes(_song(1, 'A')); expect(p.warteschlange.length, 1); expect(p.aktuellerIndex, -1); }); test('amEndeHinzufuegen hängt ans Ende an', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A')]); p.amEndeHinzufuegen(_song(2, 'B')); expect(p.warteschlange.map((s) => s.id).toList(), [1, 2]); }); test('verschiebeInWarteschlange passt aktuellen Index an (Song danach)', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]); p.verschiebeInWarteschlange(2, 0); // C nach vorne expect(p.warteschlange.map((s) => s.id).toList(), [3, 1, 2]); expect(p.aktuellerIndex, 1); // aktuell A (war 0, ist jetzt 1) }); test('verschiebeInWarteschlange passt aktuellen Index an (Song davor)', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]); p.verschiebeInWarteschlange(0, 2); // aktueller Song selbst wandert expect(p.warteschlange.map((s) => s.id).toList(), [2, 3, 1]); expect(p.aktuellerIndex, 2); }); }); group('Repeat (Wiederholmodus)', () { test('aus: naechsterIndex null am Ende der Warteschlange', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')], startIndex: 2); p.setWiederholmodus(Wiederholmodus.aus); expect(p.naechsterIndex(), isNull); }); test('titel: immer der aktuelle Song', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')], startIndex: 1); p.setWiederholmodus(Wiederholmodus.titel); expect(p.naechsterIndex(), 1); expect(p.naechsterIndex(), 1); }); test('playlist: nach dem letzten Song wieder von vorn', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]); p.setWiederholmodus(Wiederholmodus.playlist); // Mittendrin: nächster ist der Folgesong expect(p.naechsterIndex(), 1); // Am Ende (letzter Song): wieder von vorn p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')], startIndex: 2); expect(p.naechsterIndex(), 0); }); }); group('Shuffle (Zufallsmodus)', () { test('spielt alle anderen Songs genau einmal, ohne den aktuellen', () { final p = PlayerService(); p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]); p.setZufallsmodus(true); final folge = []; int? idx; while ((idx = p.naechsterIndex()) != null) { folge.add(idx!); if (folge.length > 10) break; // Schutz vor Endlosschleife } expect(folge.length, 4); // 5 Songs − aktueller expect(folge.toSet(), {1, 2, 3, 4}); // keine Wiederholung, kein aktueller }); test('shuffle + playlist: mischt nach Durchlauf neu weiter', () { final p = PlayerService(); p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]); p.setZufallsmodus(true); p.setWiederholmodus(Wiederholmodus.playlist); final folge = []; for (var i = 0; i < 12; i++) { final idx = p.naechsterIndex(); expect(idx, isNotNull); folge.add(idx!); } // Nie der aktuelle Song (Index 0), alle anderen kommen vor expect(folge.every((i) => i >= 1 && i <= 4), isTrue); expect(folge.toSet(), {1, 2, 3, 4}); }); test('shuffle mit einzelner Warteschlange: playlist wiederholt den Song', () { final p = PlayerService(); p.setWarteschlange([_song(1, 'A')]); p.setZufallsmodus(true); p.setWiederholmodus(Wiederholmodus.playlist); expect(p.naechsterIndex(), 0); expect(p.naechsterIndex(), 0); }); test('shuffle schließt neuen aktuellen Index nach Umsortieren aus', () { final p = PlayerService(); p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]); p.setZufallsmodus(true); p.verschiebeInWarteschlange(0, 2); // aktueller Song wandert auf Index 2 expect(p.aktuellerIndex, 2); final folge = []; int? idx; while ((idx = p.naechsterIndex()) != null) { folge.add(idx!); if (folge.length > 10) break; } expect(folge.toSet(), {0, 1, 3, 4}); // Index 2 (aktuell) nie dabei }); }); group('Sleep-Timer', () { test('setSleepTimer aktiviert + Restzeit vorhanden', () { final p = PlayerService(); p.setSleepTimer(const Duration(minutes: 30)); expect(p.sleepTimerAktiv, isTrue); expect(p.sleepRestzeit, isNotNull); expect(p.sleepRestzeit!.inMinutes, greaterThanOrEqualTo(29)); }); test('cancelSleepTimer deaktiviert', () { final p = PlayerService(); p.setSleepTimer(const Duration(minutes: 15)); p.cancelSleepTimer(); expect(p.sleepTimerAktiv, isFalse); expect(p.sleepRestzeit, isNull); }); test('setSleepTimer ersetzt einen laufenden Timer', () { final p = PlayerService(); p.setSleepTimer(const Duration(minutes: 15)); p.setSleepTimer(const Duration(minutes: 60)); expect(p.sleepTimerAktiv, isTrue); expect(p.sleepRestzeit!.inMinutes, greaterThanOrEqualTo(59)); }); }); }