import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:rippr/main.dart' as app; import 'package:rippr/src/app/providers.dart'; /// Drives a whole recording on a simulator that is genuinely *moving*. /// /// Run `xcrun simctl location booted start --speed=15 ` first. The iOS /// simulator interpolates between waypoints over time, so the device genuinely moves — /// but **CoreLocation still reports speed 0.0** throughout. Measured, not assumed: a /// probe run captured 8 fixes, every one of them zero. /// /// So position, distance, path shape and segment structure are all verifiable here. /// Speed, moving time and speed colouring are not, on either platform. /// /// The app is uninstalled when the run ends, taking its database with it, so anything /// worth knowing is read back through the provider graph and printed from inside. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('record a ride end to end through the UI', (tester) async { app.main(); await tester.pumpAndSettle(const Duration(seconds: 5)); await tester.tap(find.byKey(const Key('start'))); await tester.pumpAndSettle(const Duration(seconds: 2)); // Ride for a while, letting the flush timer land batches. for (var i = 0; i < 15; i++) { await tester.runAsync( () => Future.delayed(const Duration(seconds: 1))); await tester.pump(); } await tester.tap(find.byKey(const Key('stop'))); await tester.pumpAndSettle(const Duration(seconds: 3)); // Read the stored ride back through the real provider graph. The app is // uninstalled when the run ends, taking its database with it, so anything worth // knowing has to be reported from inside the test. final container = ProviderScope.containerOf( tester.element(find.byType(MaterialApp)), listen: false, ); final repo = container.read(tripRepositoryProvider); final trips = await repo.watchCompletedTrips().first; debugPrint('RIPPR-PROBE completedTrips=${trips.length}'); for (final t in trips) { final points = await repo.pointsForTrip(t.id); final segments = await repo.segmentsForTrip(t.id); final lats = [for (final p in points) p.latitude]; debugPrint('RIPPR-PROBE trip=${t.id} points=${t.pointCount} ' 'distanceM=${t.distanceM.toStringAsFixed(1)} ' 'maxSpeedKmh=${t.maxSpeedKmh.toStringAsFixed(1)} ' 'movingMs=${t.movingMillis} segments=${segments.length}'); if (lats.isNotEmpty) { debugPrint('RIPPR-PROBE latSpan=${(lats.reduce((a, b) => a > b ? a : b) - lats.reduce((a, b) => a < b ? a : b)).toStringAsFixed(5)}'); } } // What CAN be proven locally: the ride persisted, moved, and is listed. expect(trips, isNotEmpty, reason: 'the ride was not saved'); expect(trips.first.pointCount, greaterThan(0)); expect(trips.first.distanceM, greaterThan(0), reason: 'the simulator moved, so distance must be non-zero'); // Now the list, then the detail screen with its map. await tester.tap(find.text('Rides')); await tester.pumpAndSettle(const Duration(seconds: 2)); expect(find.byType(Card), findsWidgets, reason: 'the ride should be listed'); await tester.tap(find.byKey(Key('trip-${trips.first.id}'))); await tester.pumpAndSettle(const Duration(seconds: 4)); expect(find.text('DISTANCE'), findsOneWidget); debugPrint('RIPPR-PROBE detail screen rendered'); // Hold here so an external `simctl io screenshot` can capture the detail screen and // its map. Tapping the iOS simulator is not scriptable, so this is how a visual // check gets taken at all. for (var i = 0; i < 14; i++) { await tester.runAsync( () => Future.delayed(const Duration(seconds: 1))); await tester.pump(); } }); }