159 lines
5.0 KiB
Dart
159 lines
5.0 KiB
Dart
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() {
|
|
final base = DateTime.utc(2026, 9, 19);
|
|
|
|
test(
|
|
'normal READY launch retains interpolation fallback and null-speed handling',
|
|
() async {
|
|
final db = AppDatabase.forTesting(NativeDatabase.memory());
|
|
final controller = StreamController<TelemetrySample>(sync: true);
|
|
final timer = PerformanceTimerService(db, controller.stream);
|
|
addTearDown(() async {
|
|
timer.dispose();
|
|
await controller.close();
|
|
await db.close();
|
|
});
|
|
|
|
timer.setTarget(PerformanceTarget.sixty);
|
|
for (final age in <int?>[null, 0, 4001]) {
|
|
controller.add(TelemetrySample(timestamp: base, gpsAgeMs: age));
|
|
expect(timer.state.state, PerformanceState.ready);
|
|
}
|
|
// No previous speed sample: launch time falls back to this timestamp.
|
|
controller.add(TelemetrySample(timestamp: base, speed: 5, gpsAgeMs: 0));
|
|
expect(timer.state.state, PerformanceState.timing);
|
|
for (final age in <int?>[null, 0, 4001]) {
|
|
controller.add(
|
|
TelemetrySample(
|
|
timestamp: base.add(const Duration(milliseconds: 100)),
|
|
gpsAgeMs: age,
|
|
),
|
|
);
|
|
expect(timer.state.state, PerformanceState.timing);
|
|
}
|
|
controller.add(
|
|
TelemetrySample(
|
|
timestamp: base.add(const Duration(seconds: 3)),
|
|
speed: 60,
|
|
gpsAgeMs: 0,
|
|
),
|
|
);
|
|
expect(timer.state.state, PerformanceState.completed);
|
|
final runs = await db.select(db.performanceRuns).get();
|
|
expect(runs, hasLength(1));
|
|
expect(runs.single.timeMs, 3000);
|
|
},
|
|
);
|
|
|
|
test('stale READY requires new stationary evidence before launch', () async {
|
|
final db = AppDatabase.forTesting(NativeDatabase.memory());
|
|
final controller = StreamController<TelemetrySample>(sync: true);
|
|
final timer = PerformanceTimerService(db, controller.stream);
|
|
addTearDown(() async {
|
|
timer.dispose();
|
|
await controller.close();
|
|
await db.close();
|
|
});
|
|
void emit(int ms, double speed, int age) {
|
|
controller.add(
|
|
TelemetrySample(
|
|
timestamp: base.add(Duration(milliseconds: ms)),
|
|
speed: speed,
|
|
gpsAgeMs: age,
|
|
),
|
|
);
|
|
}
|
|
|
|
timer.setTarget(PerformanceTarget.sixty);
|
|
emit(0, 0, 0);
|
|
expect(timer.state.state, PerformanceState.ready);
|
|
emit(4000, 0, 4000);
|
|
expect(timer.state.state, PerformanceState.ready);
|
|
emit(4001, 0, 4001);
|
|
expect(timer.state.state, PerformanceState.idle);
|
|
emit(5000, 30, 5000);
|
|
emit(6000, 40, 0);
|
|
expect(timer.state.state, PerformanceState.idle);
|
|
emit(9000, 65, 0);
|
|
expect(timer.state.state, PerformanceState.idle);
|
|
expect(await db.select(db.performanceRuns).get(), isEmpty);
|
|
|
|
emit(10000, 0, 0);
|
|
expect(timer.state.state, PerformanceState.ready);
|
|
emit(10200, 5, 0);
|
|
expect(timer.state.state, PerformanceState.timing);
|
|
emit(13000, 65, 0);
|
|
expect(timer.state.state, PerformanceState.completed);
|
|
final runs = await db.select(db.performanceRuns).get();
|
|
expect(runs, hasLength(1));
|
|
expect(runs.single.timeMs, closeTo(2727, 1));
|
|
});
|
|
|
|
for (final coordinates in <(double?, double?)>[
|
|
(null, null),
|
|
(null, 0),
|
|
(0, null),
|
|
(double.nan, 0),
|
|
(0, double.nan),
|
|
(double.infinity, 0),
|
|
(0, double.negativeInfinity),
|
|
(90.1, 0),
|
|
(-90.1, 0),
|
|
(0, 180.1),
|
|
(0, -180.1),
|
|
]) {
|
|
for (final metadata in ['absent', 'age', 'timestamp']) {
|
|
test('invalid coordinates $coordinates with $metadata are not fresh', () {
|
|
expect(
|
|
TelemetrySample(
|
|
timestamp: base,
|
|
latitude: coordinates.$1,
|
|
longitude: coordinates.$2,
|
|
gpsAgeMs: metadata == 'age' ? 0 : null,
|
|
gpsTimestamp: metadata == 'timestamp' ? base : null,
|
|
).isGpsFresh,
|
|
isFalse,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
for (final age in [3999, 4000, 4001]) {
|
|
for (final useAge in [true, false]) {
|
|
test('GPS boundary $age with explicit age $useAge', () {
|
|
expect(
|
|
TelemetrySample(
|
|
timestamp: base,
|
|
latitude: 0,
|
|
longitude: 0,
|
|
gpsAgeMs: useAge ? age : null,
|
|
gpsTimestamp: useAge
|
|
? null
|
|
: base.subtract(Duration(milliseconds: age)),
|
|
).isGpsFresh,
|
|
age <= 4000,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
test('valid coordinate limits retain metadata-free compatibility', () {
|
|
for (final coordinates in [(90.0, 180.0), (-90.0, -180.0), (0.0, 0.0)]) {
|
|
expect(
|
|
TelemetrySample(
|
|
timestamp: base,
|
|
latitude: coordinates.$1,
|
|
longitude: coordinates.$2,
|
|
).isGpsFresh,
|
|
isTrue,
|
|
);
|
|
}
|
|
});
|
|
}
|