FB-05: closed-loop routes in Route Planner

Add an isClosedLoop toggle to route plans (schema v3 -> v4 migration),
threaded through the domain model, repository distance recomputation,
the overflow menu, and the drawn polyline, so a route can loop back to
its first waypoint.
This commit is contained in:
2026-08-24 16:22:51 -05:00
parent 57bc3c9233
commit 9ac0be6ee5
9 changed files with 359 additions and 8 deletions

View File

@@ -274,6 +274,78 @@ void main() {
expect(find.textContaining('MB'), findsOneWidget);
});
screenTest('the "Close the loop" menu item is disabled with 0-1 waypoints, '
'enabled with 2+ (FB-05)', (tester) async {
final id = await repo.createRoutePlan(1000);
await repo.addWaypoint(id, 51.0, -114.0);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
var item = tester.widget<PopupMenuItem<String>>(
find.byKey(const Key('toggle-closed-loop')),
);
expect(item.enabled, isFalse);
// Close the (still-open) menu and add a second waypoint.
await tester.tapAt(const Offset(10, 10));
await tester.pump(const Duration(milliseconds: 300));
await repo.addWaypoint(id, 51.01, -114.0);
await tester.pump(const Duration(milliseconds: 50));
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
item = tester.widget<PopupMenuItem<String>>(
find.byKey(const Key('toggle-closed-loop')),
);
expect(item.enabled, isTrue);
});
screenTest('tapping "Close the loop" toggles isClosedLoop and flips the label '
'(FB-05)', (tester) async {
final id = await repo.createRoutePlan(1000);
await repo.addWaypoint(id, 51.0, -114.0);
await repo.addWaypoint(id, 51.01, -114.0);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
expect(find.text('Close the loop'), findsOneWidget);
await tester.tap(find.byKey(const Key('toggle-closed-loop')));
await tester.pump(const Duration(milliseconds: 300));
final route = await repo.routePlanById(id);
expect(route?.isClosedLoop, isTrue);
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
expect(find.text('Open the loop'), findsOneWidget);
});
screenTest('a closed loop draws one more polyline point than there are waypoints '
'(FB-05)', (tester) async {
final id = await repo.createRoutePlan(1000);
await repo.addWaypoint(id, 51.0, -114.0);
await repo.addWaypoint(id, 51.01, -114.0);
await repo.addWaypoint(id, 51.02, -114.0);
await repo.setClosedLoop(id, true);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
final layer = tester.widget<PolylineLayer>(find.byType(PolylineLayer));
expect(layer.polylines.single.points, hasLength(4));
});
screenTest('the shell nav bar correctly shows Plan active on this screen '
'(UI-06)', (tester) async {
final id = await repo.createRoutePlan(1000);