From 7dfdbf115938734800eeede8f380e3e77bf50fc9 Mon Sep 17 00:00:00 2001 From: Bernd Steckmeister Date: Fri, 3 Jul 2026 22:53:26 +0200 Subject: [PATCH] =?UTF-8?q?test:=20Vertragsnetz=20f=C3=BCr=20die=20Storage?= =?UTF-8?q?-Fassade=20settings=5Fprefs.dart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8 Tests mit dem offiziellen SharedPreferences-In-Memory-Mock: BoolPref, StringSetPref, Theme-Prefs (Roundtrip/Teil-Save), Server-Banner inkl. korruptes-JSON-Fehlerpfad. Sichert die Schnittstelle ab, die beim M2-Storage-Umbau zur Pflicht-Fassade wird. Alle 22 Tests grün auf dem Pi. Co-Authored-By: Claude Fable 5 --- CHANGES.md | 2 + PROGRESS.md | 19 +++++-- test/settings_prefs_test.dart | 103 ++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 test/settings_prefs_test.dart diff --git a/CHANGES.md b/CHANGES.md index 8075042..4c83866 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1209,3 +1209,5 @@ 2026-07-03 22:50 [Write] /home/steggi/pyramid/test/auth_log_test.dart 2026-07-03 22:50 [Edit] /home/steggi/pyramid/pubspec.yaml 2026-07-03 22:51 [Edit] /home/steggi/pyramid/PROGRESS.md +2026-07-03 22:52 [Write] /home/steggi/pyramid/test/settings_prefs_test.dart +2026-07-03 22:53 [Edit] /home/steggi/pyramid/PROGRESS.md diff --git a/PROGRESS.md b/PROGRESS.md index 4e6edbe..4f96b8e 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -34,16 +34,27 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format: 500-Zeilen-Kappung (älteste Einträge fliegen raus, neueste überleben). Dafür `path_provider_platform_interface ^2.1.2` als dev_dependency ergänzt – offizieller Mock-Weg, war schon transitiv im Lockfile, kein neues externes - Paket. `flutter test`: **alle 14 Tests grün auf dem Pi**, `flutter analyze` + Paket. `flutter test`: alle 14 Tests grün, `flutter analyze` unverändert (1 bekannter Hinweis `chat_provider.dart:42`). +- **`test/settings_prefs_test.dart` (8 Tests):** `core/settings_prefs.dart` + ist die Storage-Fassade, die laut `docs/M2_MODULE_SCHNITT.md` zur + Pflicht-Schnittstelle ausgebaut werden soll – dieses Vertragsnetz muss beim + M2-Umbau stehen bleiben. Getestet mit dem offiziellen In-Memory-Mock + (`SharedPreferences.setMockInitialValues`, kein neues Paket): `BoolPref` + (persistierter Wert überstimmt Default, set/toggle persistieren), + `StringSetPref` (Laden/add/remove, No-Ops bei Duplikat/Fehlendem), + Theme-Prefs (Defaults, Roundtrip, Teil-Save lässt Rest unangetastet), + Server-Banner (Roundtrip, Entfernen per null/leer, **korruptes JSON liefert + leere Map statt Crash** – echter Fehlerpfad). `flutter test`: **alle + 22 Tests grün auf dem Pi.** **Offen/Nächster Schritt:** Unverändert blockiert: Alle offenen ROADMAP-Punkte (M0 SQLCipher, M1 Härtetest, M2 Call-Schicht/State-Management) brauchen PC/GUI oder Bernds Antworten (Call-Pilot, SQLCipher-Priorität) – siehe `docs/PC_TESTPLAN.md` und die Fragen an Bernd weiter unten. Weitere sichere -Pi-Testkandidaten sind erschöpft (Rest ist bibliotheksprivat oder braucht -Widget-Tests mit gemocktem Matrix-Client – lohnt erst nach dem M2-Modulschnitt -mit echten öffentlichen Schnittstellen). +Pi-Testkandidaten sind jetzt wirklich erschöpft (Rest ist bibliotheksprivat +oder braucht Widget-Tests mit gemocktem Matrix-Client – lohnt erst nach dem +M2-Modulschnitt mit echten öffentlichen Schnittstellen). **Stolperfallen:** `AuthLog` cached sein File-Handle statisch – der Test für „Plattform kaputt" muss deshalb VOR den Tests mit funktionierender Plattform diff --git a/test/settings_prefs_test.dart b/test/settings_prefs_test.dart new file mode 100644 index 0000000..a28684d --- /dev/null +++ b/test/settings_prefs_test.dart @@ -0,0 +1,103 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:pyramid/core/settings_prefs.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Tests für die Storage-Fassade, die laut M2-Modulschnitt zur Pflicht- +/// Schnittstelle für alle Einstellungs-Zugriffe ausgebaut werden soll – +/// dieses Vertragsnetz muss bei dem Umbau stehen bleiben. +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + // Frischer In-Memory-Store pro Test (setzt auch die gecachte + // SharedPreferences-Instanz zurück). + SharedPreferences.setMockInitialValues({}); + }); + + group('BoolPref', () { + test('lädt persistierten Wert und überstimmt damit den Default', () async { + SharedPreferences.setMockInitialValues({'notif_sound': false}); + final pref = BoolPref('notif_sound', true); + expect(pref.state, true, reason: 'Default gilt, bis geladen ist'); + await pumpEventQueue(); + expect(pref.state, false, reason: 'persistierter Wert muss gewinnen'); + }); + + test('set/toggle ändern Zustand und persistieren', () async { + final pref = BoolPref('notif_desktop', true); + await pumpEventQueue(); + pref.toggle(); + await pumpEventQueue(); + expect(pref.state, false); + final p = await SharedPreferences.getInstance(); + expect(p.getBool('notif_desktop'), false); + }); + }); + + group('StringSetPref', () { + test('lädt persistierte Liste als Set, add/remove persistieren', () async { + SharedPreferences.setMockInitialValues({ + 'muted_rooms': ['!a:hs', '!b:hs'], + }); + final pref = StringSetPref('muted_rooms'); + await pumpEventQueue(); + expect(pref.state, {'!a:hs', '!b:hs'}); + + await pref.add('!c:hs'); + await pref.remove('!a:hs'); + final p = await SharedPreferences.getInstance(); + expect(p.getStringList('muted_rooms')!.toSet(), {'!b:hs', '!c:hs'}); + }); + + test('doppeltes add und remove von Fehlendem sind No-Ops', () async { + final pref = StringSetPref('muted_rooms'); + await pumpEventQueue(); + await pref.add('!a:hs'); + await pref.add('!a:hs'); + await pref.remove('!gibtsnicht:hs'); + expect(pref.state, {'!a:hs'}); + }); + }); + + group('Theme-Prefs', () { + test('leerer Store liefert die dokumentierten Defaults', () async { + final t = await loadThemePrefs(); + expect(t.isDark, true); + expect(t.accentIdx, 0); + expect(t.radius, 12.0); + expect(t.motion, 1.0); + }); + + test('Roundtrip: gespeicherte Werte kommen zurück, Teil-Save lässt ' + 'den Rest unangetastet', () async { + await saveThemePrefs(isDark: false, accentIdx: 3, radius: 8, motion: 0.5); + var t = await loadThemePrefs(); + expect((t.isDark, t.accentIdx, t.radius, t.motion), (false, 3, 8.0, 0.5)); + + // Nur ein Feld ändern – die anderen dürfen nicht zurückfallen. + await saveThemePrefs(accentIdx: 1); + t = await loadThemePrefs(); + expect((t.isDark, t.accentIdx, t.radius, t.motion), (false, 1, 8.0, 0.5)); + }); + }); + + group('Server-Banner', () { + test('Roundtrip + Entfernen per null/leerem mxcUri', () async { + await saveServerBanner('https://hs1', 'mxc://hs1/abc'); + await saveServerBanner('https://hs2', 'mxc://hs2/def'); + expect(await loadAllServerBanners(), + {'https://hs1': 'mxc://hs1/abc', 'https://hs2': 'mxc://hs2/def'}); + + await saveServerBanner('https://hs1', null); + await saveServerBanner('https://hs2', ''); + expect(await loadAllServerBanners(), isEmpty); + }); + + test('korruptes JSON im Store crasht nicht, sondern liefert leere Map', + () async { + SharedPreferences.setMockInitialValues( + {'server_banners': '{kein json'}); + expect(await loadAllServerBanners(), isEmpty); + }); + }); +}