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/recap_service_test.dart
Dustin 22ce611537 v2.53 — Sprint E: Recap-Zeiträume (Woche/Monat/Jahr)
## Recap-Zeiträume
- recap_service.dart: RecapZeitraum-Enum (woche/monat/jahr), berechne(zeitraum:, jetzt:) mit injizierbarem Historie-Loader (Tests ohne DB), Fallback-Kette Woche → Monat → Jahr → Gesamt-Historie, Zeitraum-Getter (kopfzeile, introTitel, introUntertitel, zeitraumLabel, shareTitel, Monatsname)
- recap_screen.dart: Zeitraum-Umschalter (7 Tage / Monat / Jahr) oben im Recap, dynamische Texte („Deine Woche in Musik“, „Abspielungen in dieser Woche“, „Melo Wochen-Recap“), Seiten-Reset bei Wechsel
- home_screen.dart: Recap-Karte „Recap 2026“ + „Woche · Monat · Jahr“-Untertitel
- settings_screen.dart: Kachel „Recap (Woche/Monat/Jahr)“
- test/recap_service_test.dart: 12 neue Tests (Filter, Fallback-Kette, Aggregation, Label-Getter)
- flutter analyze 0 Issues, Tests 123/123
2026-08-05 11:10:49 +02:00

221 lines
7.5 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/services/recap_service.dart';
/// RecapService-Tests mit injiziertem Historie-Loader (kein DB-Zugriff).
/// Abgedeckt: Zeitraum-Filter (Woche/Monat/Jahr), Fallback-Kette
/// (Woche → Monat → Jahr → Gesamt), Aggregation + Label-Getter.
void main() {
// Fester Anker-Zeitpunkt: 5. August 2026, 15:00 Uhr
final jetzt = DateTime(2026, 8, 5, 15, 0, 0);
Map<String, dynamic> zeile(
int id,
String playedAt, {
String titel = 'Song',
String kuenstler = 'Künstler',
int dauer = 200,
}) =>
{
'song_id': id,
'played_at': playedAt,
'titel': titel,
'kuenstler': kuenstler,
'dauer_sekunden': dauer,
};
RecapService service(List<Map<String, dynamic>> rows) =>
RecapService(historie: () async => rows);
test('leere History → null (alle Zeiträume)', () async {
expect(await service([]).berechne(jetzt: jetzt), isNull);
expect(await service([]).berechne(zeitraum: RecapZeitraum.woche, jetzt: jetzt),
isNull);
expect(
await service([])
.berechne(zeitraum: RecapZeitraum.monat, jetzt: jetzt),
isNull);
});
group('Jahr', () {
test('nur Einträge des aktuellen Jahres zählen', () async {
final rows = [
zeile(1, '2026-03-01T10:00:00'),
zeile(2, '2026-08-01T10:00:00'),
zeile(3, '2025-12-31T23:59:00'), // anderes Jahr → raus
];
final d = await service(rows).berechne(jetzt: jetzt);
expect(d, isNotNull);
expect(d!.gesamtAnzahl, 2);
expect(d.zeitraum, RecapZeitraum.jahr);
});
test('Jahr ohne Daten → Gesamt-Historie (alle Einträge)', () async {
final rows = [
zeile(1, '2024-01-01T10:00:00'),
zeile(2, '2023-06-15T10:00:00'),
];
final d = await service(rows).berechne(jetzt: jetzt);
expect(d, isNotNull);
expect(d!.gesamtAnzahl, 2); // Fallback auf Gesamt
});
});
group('Monat', () {
test('nur Einträge des aktuellen Kalendermonats zählen', () async {
final rows = [
zeile(1, '2026-08-01T10:00:00'),
zeile(2, '2026-08-05T08:00:00'),
zeile(3, '2026-07-31T23:59:00'), // Juli → raus
zeile(4, '2026-01-15T10:00:00'), // Januar → raus
];
final d =
await service(rows).berechne(zeitraum: RecapZeitraum.monat, jetzt: jetzt);
expect(d, isNotNull);
expect(d!.gesamtAnzahl, 2);
expect(d.zeitraum, RecapZeitraum.monat);
expect(d.monatsName, 'August');
});
test('Monat ohne Daten → nächstgrößerer Zeitraum (Jahr)', () async {
final rows = [
zeile(1, '2026-03-01T10:00:00'),
zeile(2, '2026-05-15T10:00:00'),
];
final d =
await service(rows).berechne(zeitraum: RecapZeitraum.monat, jetzt: jetzt);
expect(d, isNotNull);
expect(d!.zeitraum, RecapZeitraum.jahr); // Fallback Monat → Jahr
expect(d.gesamtAnzahl, 2);
});
});
group('Woche', () {
test('nur Einträge der letzten 7 Tage zählen', () async {
final rows = [
zeile(1, '2026-08-04T10:00:00'), // innerhalb 7 Tage
zeile(2, '2026-07-30T12:00:00'), // genau 6 Tage vorher → drin
zeile(3, '2026-07-29T12:00:00'), // > 7 Tage → raus
zeile(4, '2026-01-01T10:00:00'), // alt → raus
];
final d =
await service(rows).berechne(zeitraum: RecapZeitraum.woche, jetzt: jetzt);
expect(d, isNotNull);
expect(d!.gesamtAnzahl, 2);
expect(d.zeitraum, RecapZeitraum.woche);
});
test('Woche ohne Daten → Monat (nächstgrößerer Zeitraum)', () async {
// Anker 20.8.: 7-Tage-Grenze = 13.8. → 10.8. liegt im Monat, aber
// außerhalb der letzten 7 Tage.
final spaeter = DateTime(2026, 8, 20, 15, 0, 0);
final rows = [
zeile(1, '2026-08-10T10:00:00'),
];
final d = await service(rows)
.berechne(zeitraum: RecapZeitraum.woche, jetzt: spaeter);
expect(d, isNotNull);
expect(d!.zeitraum, RecapZeitraum.monat);
expect(d.gesamtAnzahl, 1);
});
test('Woche UND Monat ohne Daten → Jahr', () async {
final rows = [
zeile(1, '2026-03-01T10:00:00'),
];
final d =
await service(rows).berechne(zeitraum: RecapZeitraum.woche, jetzt: jetzt);
expect(d, isNotNull);
expect(d!.zeitraum, RecapZeitraum.jahr);
});
});
group('Aggregation', () {
test('Top 5 Songs + Top 3 Künstler + Hörzeit + Nacht-%', () async {
final rows = [
// Song A 3×, Song B 2×, Song C 1×
zeile(1, '2026-08-01T10:00:00', titel: 'A', kuenstler: 'K1', dauer: 300),
zeile(1, '2026-08-02T10:00:00', titel: 'A', kuenstler: 'K1', dauer: 300),
zeile(1, '2026-08-03T23:00:00', titel: 'A', kuenstler: 'K1', dauer: 300), // nach 22 Uhr
zeile(2, '2026-08-01T11:00:00', titel: 'B', kuenstler: 'K1', dauer: 240),
zeile(2, '2026-08-02T11:00:00', titel: 'B', kuenstler: 'K1', dauer: 240),
zeile(3, '2026-08-01T12:00:00', titel: 'C', kuenstler: 'K2', dauer: 180),
];
final d =
await service(rows).berechne(zeitraum: RecapZeitraum.monat, jetzt: jetzt);
expect(d, isNotNull);
expect(d!.gesamtAnzahl, 6);
expect(d.topSongs.length, 3);
expect(d.topSongs.first.song.titel, 'A');
expect(d.topSongs.first.anzahl, 3);
expect(d.topKuenstler.length, 2);
expect(d.topKuenstler.first.name, 'K1');
expect(d.topKuenstler.first.anzahl, 5);
// Hörzeit: 3×300 + 2×240 + 1×180 = 1560s = 26 Min
expect(d.gesamtSekunden, 1560);
expect(d.hoerzeitFormatiert, '26 Min');
// 1 von 6 Abspielungen nach 22 Uhr → 17%
expect(d.nachtProzent, 17);
expect(d.haeufigsterTag, isNotEmpty);
});
});
group('Label-Getter', () {
test('Woche: Kopfzeile, Intro, Teilen', () {
final d = RecapDaten(
zeitraum: RecapZeitraum.woche,
jahr: 2026,
monat: 8,
gesamtAnzahl: 12,
gesamtSekunden: 3600,
haeufigsterTag: 'Freitag',
nachtProzent: 30,
topSongs: [],
topKuenstler: [],
);
expect(d.kopfzeile, 'MELO RECAP · 7 TAGE');
expect(d.introTitel, '7 Tage');
expect(d.introUntertitel, 'Deine Woche in Musik');
expect(d.zeitraumLabel, 'Diese Woche');
expect(d.shareTitel, 'Melo Wochen-Recap');
});
test('Monat: Kopfzeile, Intro, Teilen', () {
final d = RecapDaten(
zeitraum: RecapZeitraum.monat,
jahr: 2026,
monat: 8,
gesamtAnzahl: 12,
gesamtSekunden: 3600,
haeufigsterTag: 'Freitag',
nachtProzent: 30,
topSongs: [],
topKuenstler: [],
);
expect(d.kopfzeile, 'MELO RECAP · AUGUST');
expect(d.introTitel, 'August');
expect(d.introUntertitel, 'Dein Monat in Musik');
expect(d.zeitraumLabel, 'Dein Monat');
expect(d.shareTitel, 'Melo Monats-Recap August');
});
test('Jahr: Kopfzeile, Intro, Teilen', () {
final d = RecapDaten(
zeitraum: RecapZeitraum.jahr,
jahr: 2026,
monat: 8,
gesamtAnzahl: 12,
gesamtSekunden: 3600,
haeufigsterTag: 'Freitag',
nachtProzent: 30,
topSongs: [],
topKuenstler: [],
);
expect(d.kopfzeile, 'MELO RECAP 2026');
expect(d.introTitel, '2026');
expect(d.introUntertitel, 'Dein Jahr in Musik');
expect(d.zeitraumLabel, 'Dein Jahr');
expect(d.shareTitel, 'Melo Jahres-Recap 2026');
});
});
}