This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/test/player_service_test.dart

339 lines
14 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter_test/flutter_test.dart';
import 'package:melo_app/config/app_config.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([]);
// spiele() mit ungültiger Quelle loggt einen Fehler — ohne Netzwerk-Send
// (sonst versucht der Test einen echten HTTP-Post an den Log-Server).
AppConfig.sendeDiagnosedaten = false;
});
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('spieleAlsNaechstes verschiebt vorhandenen Song statt zu duplizieren', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]);
p.spieleAlsNaechstes(_song(3, 'C'));
expect(p.warteschlange.map((s) => s.id).toList(), [1, 3, 2]);
expect(p.warteschlange.length, 3);
// Wiederholter Aufruf erzeugt weiterhin keine Duplikate
p.spieleAlsNaechstes(_song(3, 'C'));
expect(p.warteschlange.length, 3);
});
test('spieleAlsNaechstes verschiebt Song von vor dem aktuellen', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')], startIndex: 2);
p.spieleAlsNaechstes(_song(1, 'A'));
expect(p.warteschlange.map((s) => s.id).toList(), [2, 3, 1]);
expect(p.aktuellerIndex, 1); // aktueller Song (C) wandert auf Index 1
});
test('spieleAlsNaechstes im Shuffle: Song wird als nächstes gespielt', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C'), _song(4, 'D')]);
p.setZufallsmodus(true);
p.spieleAlsNaechstes(_song(3, 'C'));
// Kein Duplikat: alle 4 Songs genau einmal
expect(p.warteschlange.map((s) => s.id).toSet(), {1, 2, 3, 4});
expect(p.warteschlange.length, 4);
// C (jetzt direkt nach dem aktuellen) wird als nächstes gespielt
expect(p.naechsterIndex(), 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>[];
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 = <int>[];
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 + repeat aus + einzelne Warteschlange: Queue-Ende (null)', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A')]);
p.setZufallsmodus(true);
p.setWiederholmodus(Wiederholmodus.aus);
// Einzel-Song-Queue ohne Repeat darf denselben Song NICHT erneut liefern
expect(p.naechsterIndex(), isNull);
expect(p.naechsterIndex(), isNull);
});
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>[];
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('Wiedergabe-Reihenfolge (Queue zeigt echte Reihenfolge)', () {
test('normal: aktueller Song zuerst, dann der Rest der Warteschlange', () {
final p = PlayerService();
p.setWarteschlange(
[_song(1, 'A'), _song(2, 'B'), _song(3, 'C'), _song(4, 'D')],
startIndex: 1);
// NICHT die Bibliotheks-Liste [1,2,3,4] — sondern ab dem aktuellen Song
expect(p.wiedergabeReihenfolge.map((s) => s.id).toList(), [2, 3, 4]);
});
test('shuffle: folgt der Zufallsreihenfolge ab der aktuellen Position', () {
final p = PlayerService();
p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]);
p.setZufallsmodus(true);
final vorher = p.wiedergabeReihenfolge;
expect(vorher.length, 5);
expect(vorher.first.id, 1); // aktueller Song zuerst
expect(vorher.map((s) => s.id).toSet(), {1, 2, 3, 4, 5});
// Der erste kommende Eintrag ist exakt der, den naechsterIndex() spielt
// (naechsterIndex liefert Queue-Indizes, daher über die Warteschlange vergleichen)
final naechster = p.naechsterIndex();
expect(p.warteschlange[naechster!].id, vorher[1].id);
});
test('shuffle nach Songwechsel: beginnt mit dem neuen aktuellen Song', () {
final p = PlayerService();
p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]);
p.setZufallsmodus(true);
final naechster = p.naechsterIndex()!;
p.spiele(p.warteschlange[naechster]); // Shuffle-Position wird synchronisiert
final reihenfolge = p.wiedergabeReihenfolge;
expect(reihenfolge.first.id, p.warteschlange[naechster].id);
// Der beim Mischen ausgeschlossene Song (Index 0 / id 1) taucht erst
// wieder auf, wenn er manuell gewählt wird — der Rest der Rotation
// (4 Songs) ist vollständig enthalten
expect(reihenfolge.length, 4);
expect(reihenfolge.map((s) => s.id).toSet(), {2, 3, 4, 5});
});
test('shuffle mit Einzel-Song-Queue: nur der aktuelle Song', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A')]);
p.setZufallsmodus(true);
expect(p.wiedergabeReihenfolge.map((s) => s.id).toList(), [1]);
});
test('shuffle + Repeat-playlist + Einzel-Song-Queue: KEIN Duplikat', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A')]);
p.setZufallsmodus(true);
p.setWiederholmodus(Wiederholmodus.playlist);
// _shufflePos == -1 (Shuffle gerade eingeschaltet): der laufende Song
// wird separat gelistet — darf NICHT zusätzlich aus der
// Shuffle-Reihenfolge kommen (Regression: [A, A])
final reihenfolge = p.wiedergabeReihenfolge.map((s) => s.id).toList();
expect(reihenfolge, [1]);
expect(reihenfolge.toSet().length, reihenfolge.length,
reason: 'Keine Duplikate in der Wiedergabe-Reihenfolge');
});
test('verschieben (normal): ändert Wiedergabe-Reihenfolge und Queue', () {
final p = PlayerService();
p.setWarteschlange(
[_song(1, 'A'), _song(2, 'B'), _song(3, 'C'), _song(4, 'D')],
startIndex: 1);
p.verschiebeInWiedergabeReihenfolge(2, 1); // D vor C ziehen
expect(p.wiedergabeReihenfolge.map((s) => s.id).toList(), [2, 4, 3]);
expect(p.warteschlange.map((s) => s.id).toList(), [1, 2, 4, 3]);
expect(p.aktuellerIndex, 1); // aktueller Song bleibt an Position 0
});
test('verschieben (shuffle): ändert Zufallsreihenfolge, nicht die Queue', () {
final p = PlayerService();
p.setWarteschlange([for (var i = 1; i <= 5; i++) _song(i, 'S$i')]);
p.setZufallsmodus(true);
final vorher = p.wiedergabeReihenfolge;
p.verschiebeInWiedergabeReihenfolge(4, 1); // letzten vor den ersten Kommenden
final nachher = p.wiedergabeReihenfolge;
expect(nachher.length, vorher.length);
expect(nachher.map((s) => s.id).toSet(), vorher.map((s) => s.id).toSet());
expect(nachher[1].id, vorher[4].id);
expect(nachher[2].id, vorher[1].id);
// Bibliotheks-Queue bleibt unangetastet (Shuffle-Liste ist die Wiedergabe)
expect(p.warteschlange.map((s) => s.id).toList(), [1, 2, 3, 4, 5]);
// Der verschobene Song wird wirklich als Nächstes gespielt
final naechster = p.naechsterIndex();
expect(p.warteschlange[naechster!].id, nachher[1].id);
});
test('Position 0 (aktueller Song) kann nicht verschoben werden', () {
final p = PlayerService();
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]);
p.verschiebeInWiedergabeReihenfolge(0, 2); // von = aktueller Song → No-op
expect(p.warteschlange.map((s) => s.id).toList(), [1, 2, 3]);
// „Vor" den aktuellen Song ziehen landet auf Position 1 (danach)
p.verschiebeInWiedergabeReihenfolge(2, 0);
expect(p.warteschlange.map((s) => s.id).toList(), [1, 3, 2]);
});
test('leere Warteschlange: No-op', () {
final p = PlayerService();
p.verschiebeInWiedergabeReihenfolge(1, 2);
expect(p.wiedergabeReihenfolge, isEmpty);
});
});
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));
});
});
}