Location search starts a new mission; shrink and center header bar
Ortssuche verschob bisher nur die Karte - der HTML-Demonstrator beginnt bei einer Suche immer eine neue, nach dem gefundenen Ort benannte Mission (setMissionFromPlace(), aufgerufen aus doSearch()). Vorherige Aenderungen gehen dabei nicht verloren, da flushPendingAutosave() (hier: CurrentMissionMetaNotifier._flushNow()) zuerst noch ausstehende Autosaves schreibt. Die Nominatim-Suche wurde dafuer aus MapSearchControls in einen eigenstaendigen NominatimService extrahiert (analog WindService): injectable http.Client, damit sich die Suche in Tests ohne echten Netzwerkzugriff ueberschreiben laesst. CurrentMissionMetaNotifier bekam dafuer startNewFromPlace() (startNew() intern darauf umgebaut, um Duplikation zu vermeiden). Kopfleiste von 86%/64% (Plan-/Fly-Modus) auf einheitlich 70% Bildschirmbreite verkleinert - zentriert war sie durch Align(topCenter) + FractionallySizedBox bereits strukturell korrekt, wirkte bei der vollen Breite aber unausgewogen. Verifiziert: flutter analyze (0 issues), flutter test (64/64, davon 4 neue NominatimService-Tests und 1 neuer Widget-Test fuer den Missions-Reset bei Ortssuche), manuell auf Pixel_10a-Emulator - Suche nach "Rotterdam" setzt Fusszeile auf "Mission: Rotterdam" mit 0 Wegpunkten trotz zuvor bestehender Mission mit Wegpunkten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
aaec7f0f72
commit
926d805156
@@ -0,0 +1,71 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
|
||||
import 'package:dmc_app/services/geocoding/nominatim_service.dart';
|
||||
|
||||
http.Response _json(Object body) =>
|
||||
http.Response(jsonEncode(body), 200, headers: {
|
||||
'content-type': 'application/json',
|
||||
});
|
||||
|
||||
void main() {
|
||||
group('NominatimService.search', () {
|
||||
test('bevorzugt city aus address gegenueber display_name', () async {
|
||||
final client = MockClient((request) async {
|
||||
expect(request.url.queryParameters['q'], 'Amsterdam');
|
||||
return _json([
|
||||
{
|
||||
'lat': '52.37403',
|
||||
'lon': '4.88969',
|
||||
'display_name': 'Amsterdam, Noord-Holland, Netherlands',
|
||||
'address': {'city': 'Amsterdam', 'county': 'Noord-Holland'},
|
||||
},
|
||||
]);
|
||||
});
|
||||
final service = NominatimService(client: client);
|
||||
|
||||
final result = await service.search('Amsterdam');
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.label, 'Amsterdam');
|
||||
expect(result.lat, closeTo(52.37403, 0.00001));
|
||||
expect(result.lon, closeTo(4.88969, 0.00001));
|
||||
});
|
||||
|
||||
test('faellt ohne strukturierte Adresse auf display_name zurueck',
|
||||
() async {
|
||||
final client = MockClient((request) async {
|
||||
return _json([
|
||||
{
|
||||
'lat': '1.0',
|
||||
'lon': '2.0',
|
||||
'display_name': 'Somewhere Remote, Some Region',
|
||||
'address': <String, dynamic>{},
|
||||
},
|
||||
]);
|
||||
});
|
||||
final service = NominatimService(client: client);
|
||||
|
||||
final result = await service.search('remote place');
|
||||
|
||||
expect(result!.label, 'Somewhere Remote');
|
||||
});
|
||||
|
||||
test('liefert null bei leeren Ergebnissen', () async {
|
||||
final client = MockClient((request) async => _json([]));
|
||||
final service = NominatimService(client: client);
|
||||
|
||||
expect(await service.search('nowhere'), isNull);
|
||||
});
|
||||
|
||||
test('liefert null bei fehlgeschlagenem Request', () async {
|
||||
final client = MockClient((request) async => http.Response('', 500));
|
||||
final service = NominatimService(client: client);
|
||||
|
||||
expect(await service.search('anywhere'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
|
||||
import 'package:dmc_app/app_mode/app_mode_cubit.dart';
|
||||
import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart';
|
||||
import 'package:dmc_app/services/database/app_database.dart' hide DroneProfile, Mission;
|
||||
import 'package:dmc_app/services/geocoding/nominatim_service.dart';
|
||||
import 'package:dmc_app/ui/app_shell.dart';
|
||||
import 'package:dmc_app/ui/providers/current_mission_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/database_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/geocoding_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/mission_meta_provider.dart';
|
||||
import 'package:dmc_app/ui/widgets/full_value_chart.dart';
|
||||
import 'package:dmc_app/ui/widgets/halo_menu.dart';
|
||||
import 'package:dmc_app/ui/widgets/header_wind_pill.dart';
|
||||
@@ -442,6 +449,61 @@ void main() {
|
||||
expect(find.text('Location not found'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Ortssuche legt eine neue, nach dem Ort benannte Mission an',
|
||||
(tester) async {
|
||||
final client = MockClient((request) async => http.Response(
|
||||
jsonEncode([
|
||||
{
|
||||
'lat': '51.9225',
|
||||
'lon': '4.47917',
|
||||
'display_name': 'Rotterdam, South Holland, Netherlands',
|
||||
'address': {'city': 'Rotterdam'},
|
||||
},
|
||||
]),
|
||||
200,
|
||||
));
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
appDatabaseProvider.overrideWithValue(_testDatabase()),
|
||||
nominatimServiceProvider
|
||||
.overrideWithValue(NominatimService(client: client)),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: BlocProvider(
|
||||
create: (_) => AppModeCubit(),
|
||||
child: const MaterialApp(home: AppShell()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// Bestehenden Wegpunkt anlegen, damit sich pruefen laesst, dass die
|
||||
// Suche die Mission wirklich neu beginnt statt nur die Karte zu
|
||||
// verschieben (HTML-Demonstrator: setMissionFromPlace()).
|
||||
await tester.tap(find.byType(ReticleButton));
|
||||
await tester.pump();
|
||||
expect(container.read(currentMissionProvider), hasLength(1));
|
||||
|
||||
await tester.enterText(find.byType(TextField), 'Rotterdam');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.search);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(container.read(currentMissionMetaProvider).name, 'Rotterdam');
|
||||
expect(container.read(currentMissionMetaProvider).placeName, 'Rotterdam');
|
||||
expect(container.read(currentMissionProvider), isEmpty);
|
||||
|
||||
// Autosave debounced ueber 800ms - abwarten, damit nach Testende kein
|
||||
// Timer mehr aussteht (Doku 7.5).
|
||||
await tester.pump(const Duration(milliseconds: 900));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Kameraposition bleibt beim Wechsel Plan -> Fly -> Plan erhalten',
|
||||
(tester) async {
|
||||
|
||||
Reference in New Issue
Block a user