MotorSignal-App/test/map_replay_duplicate_action...

173 lines
5.3 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:motorcycle_dash/core/theme/theme_extensions.dart';
import 'package:motorcycle_dash/features/map/map_screen.dart';
import 'package:motorcycle_dash/database/database.dart';
import 'package:drift/native.dart';
import 'package:drift/drift.dart' as drift;
class _TestHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) => _MockHttpClient();
}
class _MockHttpHeaders extends Fake implements HttpHeaders {
@override
void add(String name, Object value, {bool preserveHeaderCase = false}) {}
@override
void set(String name, Object value, {bool preserveHeaderCase = false}) {}
}
class _MockHttpClient extends Fake implements HttpClient {
@override
Future<HttpClientRequest> getUrl(Uri url) async => _MockHttpRequest();
@override
Future<HttpClientRequest> openUrl(String method, Uri url) async => _MockHttpRequest();
@override
void close({bool force = false}) {}
}
class _MockHttpRequest extends Fake implements HttpClientRequest {
@override
final HttpHeaders headers = _MockHttpHeaders();
@override
bool followRedirects = true;
@override
int maxRedirects = 5;
@override
int contentLength = -1;
@override
Future<HttpClientResponse> close() async => _MockHttpResponse();
}
class _MockHttpResponse extends Fake implements HttpClientResponse {
@override
int get statusCode => 200;
@override
int get contentLength => _transparent1x1Png.length;
@override
HttpClientResponseCompressionState get compressionState =>
HttpClientResponseCompressionState.notCompressed;
@override
StreamSubscription<List<int>> listen(
void Function(List<int> event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return Stream<List<int>>.value(_transparent1x1Png).listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}
final _transparent1x1Png = <int>[
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00,
0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49,
0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
];
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late AppDatabase db;
setUp(() async {
HttpOverrides.global = _TestHttpOverrides();
db = AppDatabase.forTesting(NativeDatabase.memory());
// Insert sample ride and telemetry breadcrumbs
await db.into(db.rides).insert(
RidesCompanion.insert(
id: const drift.Value(1),
startTime: DateTime(2026, 1, 1, 10, 0),
distanceMiles: const drift.Value(15.2),
topSpeedMph: const drift.Value(75.0),
movingTime: const drift.Value(900),
stoppedTime: const drift.Value(60),
),
);
for (int i = 0; i < 10; i++) {
await db.into(db.telemetrySamples).insert(
TelemetrySamplesCompanion.insert(
rideId: 1,
timestamp: DateTime(2026, 1, 1, 10, 0, i * 5),
latitude: drift.Value(37.3861 + (i * 0.002)),
longitude: drift.Value(-122.2641 + (i * 0.002)),
speed: drift.Value(35.0 + i),
leanAngle: drift.Value(12.0),
),
);
}
});
tearDown(() async {
HttpOverrides.global = null;
await db.close();
});
Widget buildSubject() {
return ProviderScope(
overrides: [
databaseProvider.overrideWithValue(db),
],
child: MaterialApp(
theme: ThemeData.dark().copyWith(
extensions: [MotorSignalThemeColors.dark],
),
home: const MapScreen(rideId: 1),
),
);
}
testWidgets('Portrait: renders both appbar action and floating replay action button', (tester) async {
tester.view.physicalSize = const Size(360, 800);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
await tester.pumpWidget(buildSubject());
await tester.pumpAndSettle();
expect(find.byKey(const ValueKey('play_replay_appbar_button')), findsOneWidget);
expect(find.byKey(const ValueKey('fire_off_ride_preview_button')), findsOneWidget);
expect(tester.takeException(), isNull);
await tester.pumpWidget(const SizedBox());
await tester.pump(const Duration(milliseconds: 100));
});
testWidgets('Landscape: removes duplicate floating replay action to keep map unobstructed (#60)', (tester) async {
tester.view.physicalSize = const Size(800, 360);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
await tester.pumpWidget(buildSubject());
await tester.pumpAndSettle();
// Appbar replay action remains accessible
expect(find.byKey(const ValueKey('play_replay_appbar_button')), findsOneWidget);
// Floating button is hidden in landscape
expect(find.byKey(const ValueKey('fire_off_ride_preview_button')), findsNothing);
expect(tester.takeException(), isNull);
await tester.pumpWidget(const SizedBox());
await tester.pump(const Duration(milliseconds: 100));
});
}