import 'dart:async'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/database/database.dart' hide TelemetrySample; import 'package:motorcycle_dash/features/performance/performance_timer_service.dart'; import 'package:motorcycle_dash/models/telemetry_sample.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late AppDatabase db; late StreamController controller; PerformanceTimerService? timer; setUp(() { db = AppDatabase.forTesting(NativeDatabase.memory()); controller = StreamController.broadcast(); }); tearDown(() async { timer?.dispose(); await controller.close(); await db.close(); }); Future completeZeroToSixty(DateTime base) async { controller.add(TelemetrySample(timestamp: base, speed: 0.0)); await Future.delayed(const Duration(milliseconds: 10)); controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 200)), speed: 5.0)); await Future.delayed(const Duration(milliseconds: 10)); controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 1800)), speed: 45.0)); await Future.delayed(const Duration(milliseconds: 10)); controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 2800)), speed: 62.0)); await Future.delayed(const Duration(milliseconds: 50)); } test('performance run completed during active ride stores that ride ID', () async { final rideId = await db.startRide(startTime: DateTime.utc(2026, 9, 3, 12)); timer = PerformanceTimerService( db, controller.stream, activeRideIdReader: () => rideId, ); timer!.setTarget(PerformanceTarget.sixty); await completeZeroToSixty(DateTime.utc(2026, 9, 3, 12, 1)); final runs = await db.select(db.performanceRuns).get(); expect(runs, hasLength(1)); expect(runs.single.rideId, rideId); }); test('ride-specific performance query returns associated run after ride completion', () async { final rideId = await db.startRide(startTime: DateTime.utc(2026, 9, 3, 13)); timer = PerformanceTimerService( db, controller.stream, activeRideIdReader: () => rideId, ); timer!.setTarget(PerformanceTarget.sixty); await completeZeroToSixty(DateTime.utc(2026, 9, 3, 13, 1)); await db.endRide(rideId, const RidesCompanion()); final rideRuns = await (db.select(db.performanceRuns)..where((p) => p.rideId.equals(rideId))).get(); expect(rideRuns, hasLength(1)); expect(rideRuns.single.type, '0-60'); expect(rideRuns.single.rideId, rideId); }); test('standalone performance run remains valid without ride ID', () async { timer = PerformanceTimerService(db, controller.stream); timer!.setTarget(PerformanceTarget.sixty); await completeZeroToSixty(DateTime.utc(2026, 9, 3, 14)); final runs = await db.select(db.performanceRuns).get(); expect(runs, hasLength(1)); expect(runs.single.rideId, isNull); }); test('attempt that starts on ride A is not reassigned to ride B before save', () async { final rideA = await db.startRide(startTime: DateTime.utc(2026, 9, 3, 15)); final rideB = await db.startRide(startTime: DateTime.utc(2026, 9, 3, 16)); var activeRideId = rideA; timer = PerformanceTimerService( db, controller.stream, activeRideIdReader: () => activeRideId, ); final base = DateTime.utc(2026, 9, 3, 15, 1); timer!.setTarget(PerformanceTarget.sixty); controller.add(TelemetrySample(timestamp: base, speed: 0.0)); await Future.delayed(const Duration(milliseconds: 10)); controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 200)), speed: 5.0)); await Future.delayed(const Duration(milliseconds: 10)); activeRideId = rideB; controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 1800)), speed: 45.0)); await Future.delayed(const Duration(milliseconds: 10)); controller.add(TelemetrySample(timestamp: base.add(const Duration(milliseconds: 2800)), speed: 62.0)); await Future.delayed(const Duration(milliseconds: 50)); final rideARuns = await (db.select(db.performanceRuns)..where((p) => p.rideId.equals(rideA))).get(); final rideBRuns = await (db.select(db.performanceRuns)..where((p) => p.rideId.equals(rideB))).get(); expect(rideARuns, hasLength(1)); expect(rideBRuns, isEmpty); }); }