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
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user