import 'package:drift/drift.dart' show driftRuntimeOptions; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:rippr/src/data/database.dart'; import 'package:rippr/src/data/route_plan_repository.dart'; import 'package:rippr/src/data/trip_repository.dart'; import 'package:rippr/src/geo/geo.dart' as geo; /// V3-07: mirrors trip_repository_test.dart's shape -- in-memory Drift, real repository, /// no mocks. A route plan has no state machine, so most of what is tested here is /// straight-line distance staying correct after every mutation. void main() { late AppDatabase db; late RoutePlanRepository routes; late TripRepository trips; setUp(() { driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; db = AppDatabase(NativeDatabase.memory()); routes = RoutePlanRepository(db); trips = TripRepository(db); }); tearDown(() async => db.close()); test('a new route plan has no waypoints and zero distance', () async { final id = await routes.createRoutePlan(1000); final plan = await routes.routePlanById(id); expect(plan, isNotNull); expect(plan!.distanceM, 0.0); expect(await routes.waypointsFor(id), isEmpty); }); test('adding waypoints appends in order and updates distance live', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.addWaypoint(id, 51.02, -114.0); final waypoints = await routes.waypointsFor(id); expect(waypoints.map((w) => w.ordinal), [0, 1, 2]); final expected = geo.pathLengthMeters([ for (final w in waypoints) geo.LatLon(w.latitude, w.longitude), ]); final plan = await routes.routePlanById(id); expect(plan!.distanceM, expected); expect(expected, greaterThan(0)); }); test('moving a waypoint updates distance', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); final before = (await routes.routePlanById(id))!.distanceM; final target = (await routes.waypointsFor(id)).last; await routes.moveWaypoint(id, target.id, 52.0, -114.0); final after = (await routes.routePlanById(id))!.distanceM; expect(after, isNot(before)); }); test('deleting a waypoint closes the ordinal gap and updates distance', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.addWaypoint(id, 51.02, -114.0); final middle = (await routes.waypointsFor(id))[1]; await routes.deleteWaypoint(id, middle.id); final remaining = await routes.waypointsFor(id); expect(remaining.length, 2); expect(remaining.map((w) => w.ordinal), [0, 1]); final expected = geo.pathLengthMeters([ for (final w in remaining) geo.LatLon(w.latitude, w.longitude), ]); expect((await routes.routePlanById(id))!.distanceM, expected); }); test('reordering renumbers ordinals and leaves distance internally consistent', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.addWaypoint(id, 51.02, -114.0); await routes.reorderWaypoint(id, 0, 2); final reordered = await routes.waypointsFor(id); expect(reordered.map((w) => w.ordinal), [0, 1, 2]); expect(reordered.map((w) => w.latitude), [51.01, 51.02, 51.0]); }); test('renaming updates the row', () async { final id = await routes.createRoutePlan(1000); await routes.renameRoutePlan(id, 'Coast loop'); expect((await routes.routePlanById(id))!.name, 'Coast loop'); }); test('deleting a route plan cascades to its waypoints', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.deleteRoutePlan(id); expect(await routes.routePlanById(id), isNull); expect(await db.waypointsForRoute(id), isEmpty); }); test('setClosedLoop(true) adds the closing segment to distance', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.addWaypoint(id, 51.02, -114.0); final open = (await routes.routePlanById(id))!.distanceM; final waypoints = await routes.waypointsFor(id); final closingSegment = geo.pathLengthMeters([ geo.LatLon(waypoints.last.latitude, waypoints.last.longitude), geo.LatLon(waypoints.first.latitude, waypoints.first.longitude), ]); await routes.setClosedLoop(id, true); final closed = (await routes.routePlanById(id))!.distanceM; expect(closed, closeTo(open + closingSegment, 1e-6)); expect((await routes.routePlanById(id))!.isClosedLoop, isTrue); }); test('setClosedLoop(false) after true returns distance to the open total', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.addWaypoint(id, 51.02, -114.0); final open = (await routes.routePlanById(id))!.distanceM; await routes.setClosedLoop(id, true); expect((await routes.routePlanById(id))!.distanceM, isNot(open)); await routes.setClosedLoop(id, false); expect((await routes.routePlanById(id))!.distanceM, closeTo(open, 1e-6)); expect((await routes.routePlanById(id))!.isClosedLoop, isFalse); }); test('adding a waypoint to a closed-loop route keeps the closing segment correct', () async { final id = await routes.createRoutePlan(1000); await routes.addWaypoint(id, 51.0, -114.0); await routes.addWaypoint(id, 51.01, -114.0); await routes.setClosedLoop(id, true); // Adding a third waypoint should recompute the closing segment against the new // last waypoint, not the old one. await routes.addWaypoint(id, 51.02, -114.0); final waypoints = await routes.waypointsFor(id); final expected = geo.pathLengthMeters([ for (final w in waypoints) geo.LatLon(w.latitude, w.longitude), geo.LatLon(waypoints.first.latitude, waypoints.first.longitude), ]); expect((await routes.routePlanById(id))!.distanceM, closeTo(expected, 1e-6)); }); test('route plans never appear alongside trips and never affect ride totals', () async { await routes.createRoutePlan(1000, name: 'Plan A'); final routeId = await routes.createRoutePlan(2000, name: 'Plan B'); await routes.addWaypoint(routeId, 51.0, -114.0); await routes.addWaypoint(routeId, 52.0, -114.0); final h = await trips.startTrip(3000); await trips.appendPoints([]); await trips.completeTrip(4000); final completedTrips = await trips.db.watchCompletedTrips().first; expect(completedTrips.length, 1); expect(completedTrips.single.id, h.tripId); // Nothing about a route plan's distance leaked into the trip. expect(completedTrips.single.distanceM, 0.0); }); }