v2.55.2 — Manueller Sync: Status-Popup, Hintergrund-Weiterlauf, Sperrbildschirm-Notification

- cloud_screen.dart: Sync-Popup mit Live-Fortschritt („12/325 synchronisiert"),
  „Im Hintergrund fortsetzen"-Button, Ergebnis-Snackbar nach Abschluss
- sync_service.dart: Notification-Kanal auf HIGH + Visibility.public,
  Foreground-Service starten/stoppen während Sync (Display-aus-Garantie),
  statische syncNotificationDetails() für Tests
- main.dart: Sync-Channel-Importance auf HIGH (überschreibt alte low-Einstellung)
- Tests: +7 (syncNotificationDetails HIGH/public, Lifecycle-Unabhängigkeit)
This commit is contained in:
Dustin
2026-08-06 18:11:41 +02:00
parent b4d63509d1
commit 7777297073
4 changed files with 323 additions and 32 deletions
+81 -3
View File
@@ -1,7 +1,9 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:melo_app/services/sync_service.dart';
import 'package:melo_app/services/realtime_sync_service.dart';
/// SyncService-Tests (Sprint E, MED-3-Review-Fix).
/// SyncService-Tests (Sprint E, MED-3-Review-Fix, v2.55.2).
///
/// Abgedeckt:
/// - `tombstoneAnwenden`-Matrix (null-letzterSync / null-deletedAt /
@@ -9,8 +11,10 @@ import 'package:melo_app/services/sync_service.dart';
/// - `SyncBericht` (hatAenderungen, zusammenfassung, details)
/// - `songAusServerMap` (MED-2: Server-Listeneintrag → lokaler Song-DB-
/// Eintrag mit cloud_id + Titeldaten)
///
/// Reine, statische Funktionen — kein DB-/Plattform-Zugriff nötig.
/// - v2.55.2: `syncNotificationDetails` (Importance.high,
/// Priority.high, Visibility.public für Sperrbildschirm)
/// - v2.55.2: Sync-Service von Lifecycle unabhängig
/// (RealtimeSyncService.pausiere() ≠ SyncService stoppen)
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -174,4 +178,78 @@ void main() {
expect(song.groesseBytes, 1024);
});
});
// ─── v2.55.2: Notification-Konfiguration ───
group('syncNotificationDetails (v2.55.2: HIGH + public)', () {
test('ongoing=true → Importance.high, Priority.high, showProgress', () {
final details =
SyncService.syncNotificationDetails(ongoing: true, progress: 5, maxProgress: 25);
expect(details.importance, Importance.high);
expect(details.priority, Priority.high);
expect(details.visibility, NotificationVisibility.public);
expect(details.showProgress, isTrue);
expect(details.ongoing, isTrue);
expect(details.autoCancel, isFalse);
expect(details.progress, 5);
expect(details.maxProgress, 25);
expect(details.onlyAlertOnce, isTrue);
});
test('ongoing=false → autoCancel, kein Progress', () {
final details = SyncService.syncNotificationDetails(ongoing: false);
expect(details.importance, Importance.high);
expect(details.priority, Priority.high);
expect(details.visibility, NotificationVisibility.public);
expect(details.showProgress, isFalse);
expect(details.ongoing, isFalse);
expect(details.autoCancel, isTrue);
expect(details.progress, 0);
expect(details.maxProgress, 0);
});
test('maxProgress=0 bei ongoing=true → default 1 (Division durch 0)', () {
final details =
SyncService.syncNotificationDetails(ongoing: true, progress: 0, maxProgress: 0);
// maxProgress muss > 0 sein, sonst crasht die Notification-API
expect(details.maxProgress, 1);
});
test('channelId ist de.baka.melo.sync', () {
final details = SyncService.syncNotificationDetails(ongoing: false);
expect(details.channelId, 'de.baka.melo.sync');
});
});
// ─── v2.55.2: Lifecycle-Unabhängigkeit ───
group('SyncService unabhängig von RealtimeSyncService-Lifecycle', () {
test('laeuftGlobal wird NICHT durch pausiere() zurückgesetzt', () {
// SyncService.laeuftGlobal startet als false.
// Der Lifecycle (RealtimeSyncService.pausiere) darf diesen Zustand
// NICHT beeinflussen — es sind getrennte Systeme.
expect(SyncService.laeuftGlobal, isFalse);
// Lifecycle: App geht in den Hintergrund
RealtimeSyncService().pausiere();
// SyncService muss unabhängig bleiben
expect(SyncService.laeuftGlobal, isFalse);
});
test('laeuftNotifier wird NICHT durch pausiere() verändert', () {
final vorher = SyncService.laeuftNotifier.value;
RealtimeSyncService().pausiere();
expect(SyncService.laeuftNotifier.value, vorher);
});
test('syncBenachrichtigungGetippt wird NICHT durch pausiere() verändert', () {
final vorher = SyncService.syncBenachrichtigungGetippt.value;
RealtimeSyncService().pausiere();
expect(SyncService.syncBenachrichtigungGetippt.value, vorher);
});
});
}