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 8e9d9efc64 v2.49.5 — Play-Next verschiebt statt zu duplizieren + Shuffle-Priorität
## Fix (Code-Review Finding 3, MED)
- spieleAlsNaechstes: Song bereits in der Queue wird an Position nach dem
  aktuellen VERSCHOBEN (inkl. _aktuellerIndex-Anpassung), keine Kopie mehr
- Shuffle: keine Neu-Mischung nach Play-Next — Song wird via
  _pinneInShuffleReihenfolge an die nächste Position gepinnt (Index-
  Korrektur für Verschiebungen, _shufflePos-Nachführung am Reihenende)
- 3 Tests ergänzt: Verschieben statt Duplizieren, Song von vor dem
  aktuellen, Shuffle-Pin (kein Duplikat + naechsterIndex == gepinnter Song)
2026-08-04 16:47:29 +02:00

229 lines
8.5 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('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('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));
});
});
}