leafittome/test/widget_test.dart
cschlaefke edc4ecf405 Fix: Besitzer-Anzeige in Alt-Haushalten – eigene E-Mail wird nachgetragen
Haushalte aus der V1-Zeit haben keinen members-Eintrag für ihren Ersteller,
die Besitzer-Anzeige blieb dadurch leer. Die App trägt die eigene E-Mail
jetzt beim Start in allen eigenen Haushalten nach; die Rules erlauben dafür
nur den eigenen members-Eintrag ohne Rollenwechsel. Bis dahin zeigt die UI
„?" statt einer leeren Stelle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:50:22 +02:00

276 lines
10 KiB
Dart
Raw 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: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<void> registerDevice(User user) async {}
@override
Future<void> 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<Widget> 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<String, dynamic>?;
expect((members?['u1'] as Map<String, dynamic>?)?['email'],
'test@example.com');
expect((members?['u1'] as Map<String, dynamic>?)?['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);
});
}