Notrufnummern-Datenbasis, Ländererkennung, State-Management und Lokalisierung (AP1-AP7)

Implementiert den kompletten Kern-Flow der Emergency App gemäß docs/plan.md:
- Notrufnummern-Datenbasis für 199 Länder + Modelle/Repository (AP1)
- DialerService für tel:-Notrufe ohne Auto-Dial (AP2)
- Statisches UI mit Panik-Button-Fallback (AP3)
- Priorisierte Ländererkennung: SIM/Netz -> GPS/Geocoding -> Cache (AP4)
- EmergencyProvider verdrahtet Repository/LocationService mit der UI (AP5)
- Manuelle Länderauswahl als Fallback samt eigener Priorität (AP6)
- UI-Lokalisierung (de/en/fr/es), an erkanntes Land gekoppelt (AP7)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
root
2026-07-24 11:29:38 +02:00
co-authored by Claude Sonnet 5
parent 40e95cc495
commit e3aa088bc2
54 changed files with 7550 additions and 145 deletions
+95 -21
View File
@@ -1,30 +1,104 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:emergency/main.dart';
import 'package:emergency/app.dart';
import 'package:emergency/features/home/widgets/emergency_button.dart';
import 'package:emergency/repositories/emergency_number_repository.dart';
import 'package:emergency/services/country_code_sources/country_code_source.dart';
import 'package:emergency/services/location_service.dart';
/// Einfache manuelle Fake-Quelle statt Mocking-Package (siehe AP4-Vorgabe:
/// kein neues Mocking-Package einführen), analog zu
/// `test/services/location_service_test.dart`.
class _FakeSource implements CountryCodeSource {
_FakeSource(this._result);
final String? _result;
@override
Future<String?> resolve() async => _result;
}
/// Fake-Cache: Verhindert, dass `LocationService` beim Cachen eines
/// SIM/GPS-Treffers den echten `shared_preferences`-Plugin-Kanal anspricht
/// (der ohne Mock-Setup in reinen Widget-Tests nicht antwortet).
class _FakeCache implements CountryCodeCache {
@override
Future<String?> resolve() async => null;
@override
Future<void> save(String isoCode) async {}
}
/// Analog zu `_FakeCache`, aber für die manuelle Auswahl (AP6): verhindert
/// den echten `shared_preferences`-Zugriff, den `LocationService` seit AP6
/// als vorgeschaltete Prioritätsstufe abfragt.
class _FakeManualSource implements ManualCountryCodeStore {
@override
Future<String?> resolve() async => null;
@override
Future<void> save(String? isoCode) async {}
}
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
testWidgets('HomeScreen zeigt nach dem Laden mindestens einen EmergencyButton für DE', (
WidgetTester tester,
) async {
// Asset-Laden über rootBundle ist echte I/O und läuft nicht innerhalb des
// FakeAsync-Zeitrahmens von pump()/pumpAndSettle() durch. Deshalb wird
// die Datenbasis hier vorab über runAsync geladen (Repository.load() ist
// danach idempotent, s. Repository). Die Ländererkennung selbst wird
// über eine gefakte SIM-Quelle deterministisch auf "DE" gesetzt, damit
// der Test nicht von echten Plattform-Kanälen (SIM/GPS/Cache) abhängt.
final repository = EmergencyNumberRepository();
await tester.runAsync(() => repository.load());
// gpsSource muss explizit gesetzt werden: Ohne eigenen Wert würde
// LocationService() intern ein echtes GpsCountryCodeSource (und damit
// Geocoding()) konstruieren, was ohne registriertes Platform-Interface
// sofort wirft (siehe gps_country_code_source_test.dart). cacheSource
// wird ebenfalls gefaked, siehe _FakeCache.
final locationService = LocationService(
simSource: _FakeSource('DE'),
gpsSource: _FakeSource(null),
cacheSource: _FakeCache(),
manualSource: _FakeManualSource(),
);
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
await tester.pumpWidget(App(repository: repository, locationService: locationService));
await tester.pumpAndSettle();
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
expect(find.byType(EmergencyButton), findsWidgets);
// DE führt "112" bereits als general-Nummer, daher darf der globale
// Panik-Button hier nicht zusätzlich dieselbe Nummer anzeigen (Dedup).
expect(find.text('112'), findsOneWidget);
expect(find.byKey(const Key('panicButton')), findsNothing);
});
testWidgets(
'HomeScreen zeigt den globalen Panik-Button bereits während des Ladens der Ländererkennung',
(WidgetTester tester) async {
// Bewusst kein runAsync/Vorladen des Repositories: Der Ladezustand
// (EmergencyStatus.initializing) soll direkt nach pumpWidget
// beobachtbar sein, ohne auf das Ende des echten Asset-Ladens zu warten.
// Die Ländererkennung wird ohnehin nie erreicht (blockiert beim
// Repository-Laden), locationService wird trotzdem explizit gefaked,
// siehe Kommentar im Test oben.
await tester.pumpWidget(
App(
repository: EmergencyNumberRepository(),
locationService: LocationService(
simSource: _FakeSource(null),
gpsSource: _FakeSource(null),
cacheSource: _FakeCache(),
manualSource: _FakeManualSource(),
),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
expect(find.byKey(const Key('panicButton')), findsOneWidget);
expect(find.text('112'), findsOneWidget);
},
);
}