Add the Rippr Flutter port: source, history bundle, and installable APK
The port runs on Android and iOS and is feature-complete; the native Android app is superseded but kept, since it is still the only version that has recorded real rides. rippr-flutter-1.0-debug.apk is package com.rippr.port, deliberately different from the native com.rippr so both install side by side. Recording the same ride on both at once is the strongest available check that the port is faithful. Added INSTALL.md covering both platforms. Android is a one-line adb install; iOS has no APK equivalent and must be built and signed through Xcode with a free Apple ID, which gives a 7-day profile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
84
rippr-flutter-src/integration_test/app_test.dart
Normal file
84
rippr-flutter-src/integration_test/app_test.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:rippr/main.dart' as app;
|
||||
|
||||
/// End-to-end tests against the real app on a real device or simulator.
|
||||
///
|
||||
/// These exist to cover what widget tests structurally cannot: that Drift actually opens
|
||||
/// against platform storage, that the plugin registrations resolve, that go_router
|
||||
/// navigates a real Navigator, and that the app survives a cold start.
|
||||
///
|
||||
/// **What these still cannot prove.** Neither simulator produces velocity — `adb emu geo
|
||||
/// fix` teleports and iOS's simulated locations are no better — so max speed, moving
|
||||
/// time, average moving speed and speed colouring on the map remain unverifiable here.
|
||||
/// A green run of this file says nothing about any of them. That is what T25's real ride
|
||||
/// is for; see `docs/port/PLAN.md` and the native repo's `docs/TESTING.md`.
|
||||
///
|
||||
/// Run with:
|
||||
/// flutter test integration_test -d `<device-id>`
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('cold start', () {
|
||||
testWidgets('the app launches and lands on the record screen',
|
||||
(tester) async {
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
|
||||
expect(find.text('RIPPR'), findsOneWidget);
|
||||
expect(find.text('SPEED'), findsOneWidget);
|
||||
// Idle, because a fresh install has no ride in progress.
|
||||
expect(find.text('Ready'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('the database opens against real platform storage',
|
||||
(tester) async {
|
||||
// If Drift could not open its file, or the sqlite3 native library were missing,
|
||||
// the trips list would surface an error rather than an empty state. This is the
|
||||
// check a widget test with an in-memory database cannot make.
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
|
||||
await tester.tap(find.text('Rides'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('No rides yet'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('navigation', () {
|
||||
testWidgets('record → rides → back', (tester) async {
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
|
||||
await tester.tap(find.text('Rides'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('RIDES'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('back')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('SPEED'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('permissions', () {
|
||||
testWidgets('starting without location permission explains itself',
|
||||
(tester) async {
|
||||
// On a simulator with permission denied, Start must surface a readable message
|
||||
// rather than silently doing nothing or throwing into the void.
|
||||
//
|
||||
// If permission IS granted in the test environment, recording begins instead —
|
||||
// both are acceptable outcomes, so this asserts only that the app stays alive and
|
||||
// responsive either way.
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
|
||||
await tester.tap(find.byKey(const Key('start')));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 3));
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(find.byType(MaterialApp), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user