Files
samplez/rippr-flutter-src/integration_test/app_test.dart
Dylan 0bc42b2e5a 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>
2026-08-15 22:37:45 -05:00

85 lines
3.3 KiB
Dart

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);
});
});
}