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:
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:emergency/features/home/country_picker_screen.dart';
|
||||
import 'package:emergency/l10n/app_localizations.dart';
|
||||
import 'package:emergency/models/country.dart';
|
||||
import 'package:emergency/models/emergency_number.dart';
|
||||
import 'package:emergency/models/emergency_type.dart';
|
||||
import 'package:emergency/providers/emergency_provider.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';
|
||||
|
||||
/// Liefert eine feste Länderliste, ohne das echte JSON-Asset laden zu
|
||||
/// müssen (analog zum Fake-Repository-Muster in `emergency_provider_test.dart`).
|
||||
class _FakeRepository extends EmergencyNumberRepository {
|
||||
@override
|
||||
List<Country> get allCountries => const [
|
||||
Country(
|
||||
isoCode: 'DE',
|
||||
nameDe: 'Deutschland',
|
||||
nameEn: 'Germany',
|
||||
flag: '🇩🇪',
|
||||
numbers: [
|
||||
EmergencyNumber(type: EmergencyType.general, number: '112', labelDe: 'Notruf', labelEn: 'Emergency'),
|
||||
],
|
||||
),
|
||||
Country(
|
||||
isoCode: 'FR',
|
||||
nameDe: 'Frankreich',
|
||||
nameEn: 'France',
|
||||
flag: '🇫🇷',
|
||||
numbers: [
|
||||
EmergencyNumber(type: EmergencyType.general, number: '112', labelDe: 'Notruf', labelEn: 'Emergency'),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class _NoopSource implements CountryCodeSource {
|
||||
const _NoopSource();
|
||||
|
||||
@override
|
||||
Future<String?> resolve() async => null;
|
||||
}
|
||||
|
||||
/// Fake-Provider: überschreibt `selectCountryManually`, damit der Test ohne
|
||||
/// echte `LocationService`-Ermittlungskette prüfen kann, mit welchem
|
||||
/// ISO-Code der Screen die Auswahl auslöst.
|
||||
class _FakeProvider extends EmergencyProvider {
|
||||
_FakeProvider({required super.repository})
|
||||
: super(
|
||||
locationService: LocationService(
|
||||
simSource: const _NoopSource(),
|
||||
gpsSource: const _NoopSource(),
|
||||
),
|
||||
);
|
||||
|
||||
String? selectedIsoCode;
|
||||
int selectCallCount = 0;
|
||||
|
||||
@override
|
||||
Future<void> selectCountryManually(String isoCode) async {
|
||||
selectCallCount++;
|
||||
selectedIsoCode = isoCode;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Future<void> pumpPickerBehindAButton(WidgetTester tester, EmergencyProvider provider) async {
|
||||
// CountryPickerScreen ruft `Navigator.pop()` beim Auswählen auf – dafür
|
||||
// muss eine vorherige Route existieren, sonst wäre der Pop ein No-op.
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider<EmergencyProvider>.value(
|
||||
value: provider,
|
||||
child: MaterialApp(
|
||||
// Feste Locale, damit die (unveränderten) deutschen Text-Assertions
|
||||
// dieser Tests unabhängig von der Test-Umgebungs-/Systemsprache
|
||||
// funktionieren; das eigentliche Umschalten der App-Sprache wird
|
||||
// gezielt in `test/core/country_locale_map_test.dart` bzw.
|
||||
// `test/app_locale_test.dart` (AP7) geprüft.
|
||||
locale: const Locale('de'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const CountryPickerScreen()),
|
||||
),
|
||||
child: const Text('Öffnen'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Öffnen'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('zeigt die Länderliste an', (tester) async {
|
||||
final provider = _FakeProvider(repository: _FakeRepository());
|
||||
await pumpPickerBehindAButton(tester, provider);
|
||||
|
||||
expect(find.text('Deutschland'), findsOneWidget);
|
||||
expect(find.text('Frankreich'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Suchfeld filtert die Länderliste', (tester) async {
|
||||
final provider = _FakeProvider(repository: _FakeRepository());
|
||||
await pumpPickerBehindAButton(tester, provider);
|
||||
|
||||
await tester.enterText(find.byType(TextField), 'Deutsch');
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Deutschland'), findsOneWidget);
|
||||
expect(find.text('Frankreich'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('zeigt einen Hinweis, wenn kein Land zum Filter passt', (tester) async {
|
||||
final provider = _FakeProvider(repository: _FakeRepository());
|
||||
await pumpPickerBehindAButton(tester, provider);
|
||||
|
||||
await tester.enterText(find.byType(TextField), 'Nichtland');
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Kein Land gefunden.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Tap auf ein Land löst selectCountryManually aus und schließt den Screen', (
|
||||
tester,
|
||||
) async {
|
||||
final provider = _FakeProvider(repository: _FakeRepository());
|
||||
await pumpPickerBehindAButton(tester, provider);
|
||||
|
||||
await tester.tap(find.text('Deutschland'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(provider.selectCallCount, 1);
|
||||
expect(provider.selectedIsoCode, 'DE');
|
||||
// Screen wurde geschlossen (zurück zum "Öffnen"-Button).
|
||||
expect(find.text('Öffnen'), findsOneWidget);
|
||||
expect(find.text('Deutschland'), findsNothing);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user