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
T
Dustin c6826db651 v2.49.3 — Shuffle-Edge-Case: Einzel-Song-Queue + Repeat-aus → Queue-Ende
## Fix (Code-Review Finding 1, HIGH)
- _baueShuffleReihenfolge: Index 0 nur noch bei Repeat-playlist in die
  Shuffle-Reihenfolge aufnehmen (Einzel-Song-Queue)
- naechsterIndex: Sicherheitsnetz gegen stale Reihenfolge — Kandidat ==
  aktueller Song bei Repeat-aus liefert null (Queue-Ende) statt denselben
  Song erneut
- Test ergänzt: shuffle + repeat aus + 1-Song-Queue → null
2026-08-04 16:46:08 +02:00

198 lines
7.1 KiB
Dart
Raw 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/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>[];
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('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));
});
});
}