T22/T24/T26: uploader, integration tests, and release readiness
T22 -- TelemetryUploader on package:http with MockClient standing in for MockWebServer, tested against a real in-memory Drift database rather than a fake DAO. Upload runs on its own timer, injected as a callback so the engine has no opinion about HTTP and tests need no network. Config on shared_preferences with a hand-rolled UUID v4. Still no UI for the endpoint, exactly as in the native app. 7 tests. T24 -- integration_test/app_test.dart, 4 tests passing on the iOS simulator. These cover what widget tests cannot: Drift opening against real platform storage, plugin registration, go_router driving a real Navigator, cold start. T26 -- RELEASE-IOS.md. Usage strings are specific rather than generic, which is the leading Guideline 5.1.1 rejection cause; privacy-label answers decided; a pre-submission list covering the bundle-id switch back to com.rippr, the still default app icon, a release build, and a demo video for review notes. T25 written up as REAL-RIDE-CHECKLIST.md but outstanding by nature. Two items decide real things: force-stopping mid-ride on Android confirms the 111 km crash-gap bug is fixed, and parking 15 minutes mid-recording on iOS decides whether geolocator is sufficient or the paid engine is needed. Two prefer_initializing_formals lints suppressed with a reason: Dart forbids a named parameter beginning with an underscore, so the suggested fix will not compile. 178 tests passing, analyze clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
84
integration_test/app_test.dart
Normal file
84
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