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>
147 lines
5.2 KiB
Dart
147 lines
5.2 KiB
Dart
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);
|
||
});
|
||
}
|