import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; import 'package:firebase_auth_mocks/firebase_auth_mocks.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:leafittome/app.dart'; import 'package:leafittome/core/firebase/firebase_providers.dart'; import 'package:leafittome/core/settings/settings_provider.dart'; import 'package:leafittome/features/notifications/data/push_registration_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Tests brauchen kein echtes FCM – Push-Registrierung ist ein No-Op. class _FakePushRegistrationService implements PushRegistrationService { @override Future registerDevice(User user) async {} @override Future syncReminderTime(TimeOfDay time) async {} } /// Baut die App mit gemocktem Firebase: angemeldeter Nutzer 'u1' als Besitzer /// des Haushalts 'h1' mit einer Monstera, deren Gießen überfällig ist. /// Mit [secondHousehold] ist u1 zusätzlich Sitter im fremden Haushalt 'h2' /// (Besitzer 'u2'), in dem ein Ficus fällig ist. /// Mit [extraMember] hat 'h1' zusätzlich den Sitter 'u3'. Future buildTestApp({ String role = 'member', bool secondHousehold = false, bool extraMember = false, }) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final auth = MockFirebaseAuth( signedIn: true, mockUser: MockUser(uid: 'u1', email: 'test@example.com'), ); final firestore = FakeFirebaseFirestore(); await firestore.collection('users').doc('u1').set({ 'email': 'test@example.com', 'householdId': 'h1', }); await firestore.collection('households').doc('h1').set({ 'name': 'Mein Haushalt', 'ownerUid': 'u1', 'memberUids': ['u1', if (extraMember) 'u3'], 'members': { 'u1': {'role': role, 'email': 'test@example.com'}, if (extraMember) 'u3': {'role': 'sitter', 'email': 'sitter@example.com'}, }, }); if (secondHousehold) { await firestore.collection('households').doc('h2').set({ 'name': 'Zweiter Haushalt', 'ownerUid': 'u2', 'memberUids': ['u2', 'u1'], 'members': { 'u1': {'role': 'sitter', 'email': 'test@example.com'}, 'u2': {'role': 'member', 'email': 'other@example.com'}, }, }); await firestore .collection('households') .doc('h2') .collection('plants') .add({ 'nickname': 'Ficus', 'species': 'Ficus benjamina', 'locationId': null, 'description': '', 'careNotes': '', 'wateringIntervalDays': 7, 'fertilizingIntervalDays': 28, 'lastWatered': null, 'lastFertilized': Timestamp.fromDate( DateTime.now().subtract(const Duration(days: 3))), }); } final now = DateTime.now(); await firestore .collection('households') .doc('h1') .collection('plants') .add({ 'nickname': 'Monstera', 'species': 'Monstera deliciosa', 'locationId': null, 'description': '', 'careNotes': '', 'wateringIntervalDays': 7, 'fertilizingIntervalDays': 28, 'lastWatered': Timestamp.fromDate(now.subtract(const Duration(days: 8))), 'lastFertilized': Timestamp.fromDate(now.subtract(const Duration(days: 3))), }); return ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), firebaseAuthProvider.overrideWithValue(auth), firestoreProvider.overrideWithValue(firestore), pushRegistrationServiceProvider .overrideWithValue(_FakePushRegistrationService()), ], child: const LeafItToMeApp(), ); } void main() { testWidgets('Angemeldeter Nutzer sieht die Heute-Checkliste mit fälliger Aufgabe', (tester) async { await tester.pumpWidget(await buildTestApp()); await tester.pumpAndSettle(); expect(find.text('Heute'), findsOneWidget); expect(find.textContaining('offene Aufgabe'), findsOneWidget); expect(find.textContaining('Monstera gießen'), findsOneWidget); }); testWidgets('Aufgabe bestätigen schreibt nach Firestore und leert die Liste', (tester) async { await tester.pumpWidget(await buildTestApp()); await tester.pumpAndSettle(); await tester.tap(find.text('Erledigt').first); await tester.pumpAndSettle(); expect(find.text('Erledigt'), findsNothing); expect(find.text('Alles versorgt!'), findsOneWidget); }); testWidgets('Sitter kann abhaken, sieht aber keinen Anlegen-Button', (tester) async { await tester.pumpWidget(await buildTestApp(role: 'sitter')); await tester.pumpAndSettle(); // Abhaken ist erlaubt … expect(find.text('Erledigt'), findsWidgets); // … aber Pflanzen anlegen nicht. expect(find.text('Pflanze hinzufügen'), findsNothing); }); testWidgets('Mitglied mehrerer Haushalte kann den aktiven Haushalt wechseln', (tester) async { await tester.pumpWidget(await buildTestApp(secondHousehold: true)); await tester.pumpAndSettle(); // Über das Menü zur Haushalts-Ansicht. await tester.tap(find.byIcon(Icons.menu)); await tester.pumpAndSettle(); await tester.tap(find.text('Haushalt')); await tester.pumpAndSettle(); // Beide Haushalte sind gelistet, h1 ist aktiv, Besitzer sind ausgewiesen. expect(find.text('Meine Haushalte'), findsOneWidget); expect(find.text('Aktiv'), findsOneWidget); expect(find.text('Zweiter Haushalt'), findsOneWidget); expect(find.text('Dein Haushalt'), findsOneWidget); expect(find.text('Besitzer: other@example.com'), findsOneWidget); // Wechsel nach h2: Zeiger in users/u1 wird umgestellt … await tester.tap(find.text('Zweiter Haushalt')); await tester.pumpAndSettle(); // … und die Ansicht zeigt den neuen Haushalt (AppBar + Listeneintrag). expect(find.text('Zweiter Haushalt'), findsNWidgets(2)); expect(find.text('other@example.com'), findsOneWidget); // Im fremden Haushalt (nicht Besitzer): Austreten möglich, Entfernen nicht. expect(find.text('Haushalt verlassen'), findsOneWidget); expect(find.byIcon(Icons.person_remove), findsNothing); }); testWidgets('Checkliste gruppiert Aufgaben aus mehreren Haushalten', (tester) async { await tester.pumpWidget(await buildTestApp(secondHousehold: true)); await tester.pumpAndSettle(); // Aufgaben beider Haushalte mit Zwischenüberschriften. expect(find.textContaining('Monstera gießen'), findsOneWidget); expect(find.textContaining('Ficus gießen'), findsOneWidget); expect(find.text('Mein Haushalt'), findsOneWidget); expect(find.text('Zweiter Haushalt'), findsOneWidget); // Die Ficus-Aufgabe (zweite Gruppe) bestätigen → sie schreibt in den // richtigen Haushalt, die h2-Gruppe verschwindet mitsamt Überschriften. await tester.tap(find.text('Erledigt').last); await tester.pumpAndSettle(); expect(find.textContaining('Ficus gießen'), findsNothing); expect(find.textContaining('Monstera gießen'), findsOneWidget); expect(find.text('Zweiter Haushalt'), findsNothing); }); testWidgets('Besitzer sieht Besitzer-Kennzeichnung und kann entfernen, ' 'aber nicht austreten', (tester) async { await tester.pumpWidget(await buildTestApp(extraMember: true)); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.menu)); await tester.pumpAndSettle(); await tester.tap(find.text('Haushalt')); await tester.pumpAndSettle(); // Besitzer-Kennzeichnung am eigenen Eintrag, Entfernen-Knopf beim Sitter. expect(find.text('Mitglied · Besitzer'), findsOneWidget); expect(find.byIcon(Icons.person_remove), findsOneWidget); expect(find.text('Haushalt verlassen'), findsNothing); // Entfernen fragt nach. await tester.tap(find.byIcon(Icons.person_remove)); await tester.pumpAndSettle(); expect(find.text('Mitglied entfernen?'), findsOneWidget); }); testWidgets( 'Alt-Haushalt ohne members-Eintrag: eigene E-Mail wird nachgetragen', (tester) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final auth = MockFirebaseAuth( signedIn: true, mockUser: MockUser(uid: 'u1', email: 'test@example.com'), ); final firestore = FakeFirebaseFirestore(); await firestore.collection('users').doc('u1').set({ 'email': 'test@example.com', 'householdId': 'h1', }); // V1-Zeit: kein ownerUid, keine members-Map – nur memberUids. await firestore.collection('households').doc('h1').set({ 'name': 'Mein Haushalt', 'memberUids': ['u1'], }); await tester.pumpWidget(ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), firebaseAuthProvider.overrideWithValue(auth), firestoreProvider.overrideWithValue(firestore), pushRegistrationServiceProvider .overrideWithValue(_FakePushRegistrationService()), ], child: const LeafItToMeApp(), )); await tester.pumpAndSettle(); final doc = await firestore.collection('households').doc('h1').get(); final members = doc.data()?['members'] as Map?; expect((members?['u1'] as Map?)?['email'], 'test@example.com'); expect((members?['u1'] as Map?)?['role'], 'member'); }); testWidgets('Mitglied kann den Haushalt umbenennen', (tester) async { await tester.pumpWidget(await buildTestApp()); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.menu)); await tester.pumpAndSettle(); await tester.tap(find.text('Haushalt')); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.edit)); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).last, 'Wohnung Chris'); await tester.tap(find.text('Speichern')); await tester.pumpAndSettle(); // Der neue Name kommt live aus Firestore zurück (AppBar). expect(find.text('Wohnung Chris'), findsOneWidget); }); }