Refresh the Flutter snapshot: v3 tickets V3-04 through V3-16, plus a fresh installable APK
V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
This commit is contained in:
135
rippr-flutter-src/test/ride_notification_test.dart
Normal file
135
rippr-flutter-src/test/ride_notification_test.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
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/trip_repository.dart';
|
||||
import 'package:rippr/src/domain/models.dart';
|
||||
import 'package:rippr/src/notification/ride_notification_controller.dart';
|
||||
import 'package:rippr/src/notification/ride_notification_coordinator.dart';
|
||||
import 'package:rippr/src/notification/ride_notification_text.dart';
|
||||
import 'package:rippr/src/recording/location_source.dart';
|
||||
import 'package:rippr/src/recording/recording_engine.dart';
|
||||
|
||||
/// V3-06: the plugin call itself needs a device (see the ticket's Tests section), but the
|
||||
/// text it is given, and the routing from a tapped action back to the engine, do not. The
|
||||
/// coordinator tests check the database after an action, not the UI -- the project's
|
||||
/// standing rule for anything touching recording state.
|
||||
void main() {
|
||||
Trip trip({
|
||||
TripState state = TripState.recording,
|
||||
double distanceM = 12345,
|
||||
int movingMillis = 45 * 60 * 1000 + 12 * 1000,
|
||||
}) => Trip(
|
||||
id: 1,
|
||||
startedAt: 0,
|
||||
state: state,
|
||||
distanceM: distanceM,
|
||||
movingMillis: movingMillis,
|
||||
);
|
||||
|
||||
group('rideNotificationText', () {
|
||||
test('recording: distance and elapsed, no prefix', () {
|
||||
expect(rideNotificationText(trip()), '12.3 km · 00:45:12');
|
||||
});
|
||||
|
||||
test('paused: prefixed so the shade reads correctly without opening the app', () {
|
||||
expect(
|
||||
rideNotificationText(trip(state: TripState.paused)),
|
||||
'Paused · 12.3 km · 00:45:12',
|
||||
);
|
||||
});
|
||||
|
||||
test('honours the unit system, exactly like every other display surface', () {
|
||||
expect(
|
||||
rideNotificationText(trip(), unit: UnitSystem.imperial),
|
||||
contains('mi'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('RideNotificationCoordinator', () {
|
||||
late AppDatabase db;
|
||||
late TripRepository repo;
|
||||
late FakeLocationSource source;
|
||||
late RecordingEngine engine;
|
||||
late FakeRideNotificationController controller;
|
||||
|
||||
setUp(() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
db = AppDatabase(NativeDatabase.memory());
|
||||
repo = TripRepository(db);
|
||||
source = FakeLocationSource();
|
||||
engine = RecordingEngine(repository: repo, locationSource: source);
|
||||
controller = FakeRideNotificationController();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await source.dispose();
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('shows the notification when a trip starts and cancels it when it ends',
|
||||
() async {
|
||||
final coordinator = RideNotificationCoordinator(
|
||||
controller: controller,
|
||||
engine: engine,
|
||||
tripStream: repo.watchActiveTrip(),
|
||||
);
|
||||
|
||||
await repo.startTrip(1000);
|
||||
await pumpEventQueue();
|
||||
expect(controller.visible, isTrue);
|
||||
expect(controller.lastText, isNotNull);
|
||||
|
||||
await repo.completeTrip(2000);
|
||||
await pumpEventQueue();
|
||||
expect(controller.visible, isFalse);
|
||||
expect(controller.cancelCalls, greaterThan(0));
|
||||
|
||||
await coordinator.dispose();
|
||||
});
|
||||
|
||||
test('a tapped Pause action actually pauses the ride -- checked in the '
|
||||
'database, not the notification', () async {
|
||||
final coordinator = RideNotificationCoordinator(
|
||||
controller: controller,
|
||||
engine: engine,
|
||||
tripStream: repo.watchActiveTrip(),
|
||||
);
|
||||
await repo.startTrip(1000);
|
||||
await pumpEventQueue();
|
||||
|
||||
controller.simulateAction(RideNotificationAction.pause);
|
||||
await pumpEventQueue();
|
||||
|
||||
final active = await repo.activeTrip();
|
||||
expect(active?.state, TripState.paused);
|
||||
|
||||
await coordinator.dispose();
|
||||
});
|
||||
|
||||
test('a tapped Resume action only resumes an actually-paused trip', () async {
|
||||
final coordinator = RideNotificationCoordinator(
|
||||
controller: controller,
|
||||
engine: engine,
|
||||
tripStream: repo.watchActiveTrip(),
|
||||
);
|
||||
await repo.startTrip(1000);
|
||||
await pumpEventQueue();
|
||||
|
||||
// Recording, not paused: a Resume action here would be bogus.
|
||||
controller.simulateAction(RideNotificationAction.resume);
|
||||
await pumpEventQueue();
|
||||
expect((await repo.activeTrip())?.state, TripState.recording);
|
||||
|
||||
await repo.pauseTrip(2000);
|
||||
await pumpEventQueue();
|
||||
controller.simulateAction(RideNotificationAction.resume);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect((await repo.activeTrip())?.state, TripState.recording);
|
||||
|
||||
await coordinator.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user