V3-05: mounted (handlebar) mode
Wake lock via a WakelockController seam (mirrors LocationSource), gated strictly to actual recording and released on stop/discard/navigate-away/dispose. High-contrast ripprMountedTheme() applied only to the record screen. BigStat gained a scale param for the headline figure rather than a blanket TextTheme.apply, which crashes on Material 3's default theme.
This commit is contained in:
@@ -42,6 +42,19 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('mountedMode', () {
|
||||
test('defaults to false', () async {
|
||||
final config = await freshConfig();
|
||||
expect(config.mountedMode, isFalse);
|
||||
});
|
||||
|
||||
test('round-trips through set/get', () async {
|
||||
final config = await freshConfig();
|
||||
await config.setMountedMode(true);
|
||||
expect(config.mountedMode, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('uploadEndpoint', () {
|
||||
test('defaults to empty, not null or a placeholder', () async {
|
||||
final config = await freshConfig();
|
||||
|
||||
@@ -74,6 +74,22 @@ void main() {
|
||||
expect(config.unitSystem, UnitSystem.metric);
|
||||
});
|
||||
|
||||
testWidgets('the mounted mode switch writes through to Config (V3-05)',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(host());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final initial = tester
|
||||
.widget<SwitchListTile>(find.byKey(const Key('mounted-mode-switch')))
|
||||
.value;
|
||||
expect(initial, isFalse, reason: 'a pocketed ride is still the common case');
|
||||
|
||||
await tester.tap(find.byKey(const Key('mounted-mode-switch')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(config.mountedMode, isTrue);
|
||||
});
|
||||
|
||||
group('upload endpoint', () {
|
||||
testWidgets('a valid https URL is saved', (tester) async {
|
||||
await tester.pumpWidget(host());
|
||||
@@ -143,6 +159,10 @@ void main() {
|
||||
expect(find.text(config.deviceId), findsOneWidget);
|
||||
expect(find.byKey(const Key('copy-device-id')), findsOneWidget);
|
||||
|
||||
// The list has grown past one screen (V3-05 added a section above this), so the
|
||||
// control is not necessarily within the default test viewport.
|
||||
await tester.drag(find.byType(ListView).first, const Offset(0, -400));
|
||||
await tester.pumpAndSettle();
|
||||
// Tapping must not throw even though no real clipboard exists in the test harness.
|
||||
await tester.tap(find.byKey(const Key('copy-device-id')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:rippr/src/data/trip_repository.dart';
|
||||
import 'package:rippr/src/domain/models.dart';
|
||||
import 'package:rippr/src/ui/activity_display.dart';
|
||||
import 'package:rippr/src/recording/location_source.dart';
|
||||
import 'package:rippr/src/recording/wakelock_controller.dart';
|
||||
import 'package:rippr/src/ui/detail/trip_detail_screen.dart';
|
||||
import 'package:rippr/src/ui/format.dart';
|
||||
import 'package:rippr/src/ui/record/record_screen.dart';
|
||||
@@ -74,6 +75,8 @@ void main() {
|
||||
Widget child, {
|
||||
bool map = true,
|
||||
UnitSystem units = UnitSystem.metric,
|
||||
bool mountedMode = false,
|
||||
FakeWakelockController? wakelock,
|
||||
}) => ProviderScope(
|
||||
overrides: [
|
||||
databaseProvider.overrideWithValue(db),
|
||||
@@ -82,6 +85,10 @@ void main() {
|
||||
// tiles, which a widget test cannot serve, and it changes scroll geometry.
|
||||
mapEnabledProvider.overrideWith((ref) => map),
|
||||
unitSystemProvider.overrideWith((ref) => units),
|
||||
mountedModeProvider.overrideWith((ref) => mountedMode),
|
||||
wakelockControllerProvider.overrideWithValue(
|
||||
wakelock ?? FakeWakelockController(),
|
||||
),
|
||||
],
|
||||
child: MaterialApp(theme: ripprTheme(), home: child),
|
||||
);
|
||||
@@ -270,6 +277,77 @@ void main() {
|
||||
expect(find.byType(TileLayer), findsOneWidget,
|
||||
reason: 'foregrounding again must resume tiles');
|
||||
});
|
||||
|
||||
screenTest('mounted mode requests the wake lock while recording and releases '
|
||||
'it on stop (V3-05)', (tester) async {
|
||||
final wakelock = FakeWakelockController();
|
||||
await repo.startTrip(1000);
|
||||
await pumpLive(
|
||||
tester,
|
||||
host(const RecordScreen(), mountedMode: true, wakelock: wakelock),
|
||||
);
|
||||
|
||||
expect(wakelock.enabled, isTrue,
|
||||
reason: 'recording, mounted: the screen must not sleep');
|
||||
|
||||
await repo.completeTrip(2000);
|
||||
await tester.pump();
|
||||
|
||||
expect(wakelock.enabled, isFalse,
|
||||
reason: 'the lock must not survive the ride ending');
|
||||
});
|
||||
|
||||
screenTest('un-mounted, the wake lock is never requested (V3-05)',
|
||||
(tester) async {
|
||||
final wakelock = FakeWakelockController();
|
||||
await repo.startTrip(1000);
|
||||
await pumpLive(
|
||||
tester,
|
||||
host(const RecordScreen(), mountedMode: false, wakelock: wakelock),
|
||||
);
|
||||
|
||||
expect(wakelock.enableCalls, 0,
|
||||
reason: 'un-mounted behaviour must be exactly as before this ticket');
|
||||
});
|
||||
|
||||
screenTest('leaving the record screen releases the lock even if the ride is '
|
||||
'still active (V3-05)', (tester) async {
|
||||
final wakelock = FakeWakelockController();
|
||||
await repo.startTrip(1000);
|
||||
await pumpLive(
|
||||
tester,
|
||||
host(const RecordScreen(), mountedMode: true, wakelock: wakelock),
|
||||
);
|
||||
expect(wakelock.enabled, isTrue);
|
||||
|
||||
// Navigating away must not leak the lock -- this is the ticket's named risk: a
|
||||
// leak flattens the battery silently, after the screen that acquired it is gone.
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump();
|
||||
|
||||
expect(wakelock.enabled, isFalse);
|
||||
expect(wakelock.disableCalls, greaterThan(0));
|
||||
});
|
||||
|
||||
screenTest('the mounted theme is high-contrast and text scales up (V3-05)',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(host(const RecordScreen(), mountedMode: true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final speed = tester.widget<Text>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('speed')),
|
||||
matching: find.text('0'),
|
||||
),
|
||||
);
|
||||
expect(speed.style?.fontSize, 64 * mountedTextScale);
|
||||
expect(speed.style?.color, isNot(ripprBackground),
|
||||
reason: 'still legible, just against a different (lighter) ground');
|
||||
|
||||
final start = tester.getSize(find.byKey(const Key('start')));
|
||||
expect(start.height, 96,
|
||||
reason: 'V3-05: 72dp is not enough at speed, with gloves');
|
||||
});
|
||||
});
|
||||
|
||||
group('trips list', () {
|
||||
|
||||
Reference in New Issue
Block a user