add traction import export
All checks were successful
Release / meta (push) Successful in 3s
Release / linux-build (push) Successful in 1m1s
Release / web-build (push) Successful in 1m22s
Release / android-build (push) Successful in 6m49s
Release / release-master (push) Successful in 5s
Release / release-dev (push) Successful in 8s

This commit is contained in:
2026-01-23 13:21:08 +00:00
parent 56cc7c0902
commit 9896b6f1f8
11 changed files with 1348 additions and 2 deletions

View File

@@ -0,0 +1,83 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mileograph_flutter/components/pages/dashboard.dart';
import '../helpers/fake_services.dart';
import '../helpers/test_app.dart';
import '../helpers/test_data.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('Dashboard renders stats and panels', (tester) async {
final data = FakeDataService()
..homepageStatsValue = TestData.homepageStats
..tripsValue = TestData.tripSummaries
..onThisDayValue = TestData.onThisDayLegs()
..classClearanceProgressValue = TestData.classClearance
..latestLocoChangesValue = TestData.latestLocoChanges
..tractionValue = TestData.traction
..currentYearMileageValue = 1200.5;
final auth = FakeAuthService(api: FakeApiService());
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Dashboard(),
),
],
);
await tester.pumpWidget(
buildTestRouterApp(
router: router,
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
expect(find.textContaining('Welcome back, Alex Rider'), findsOneWidget);
expect(find.text('Top traction'), findsOneWidget);
expect(find.text('67 001'), findsOneWidget);
expect(find.text('On this day'), findsOneWidget);
expect(find.text('York → Durham'), findsOneWidget);
expect(find.text('Latest Loco Changes'), findsOneWidget);
expect(find.text('Status'), findsWidgets);
expect(find.text('Active'), findsWidgets);
});
testWidgets('Dashboard shows loading overlay without stats', (tester) async {
final data = FakeDataService()
..homepageStatsValue = null
..isHomepageLoadingValue = true;
final auth = FakeAuthService(api: FakeApiService());
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Dashboard(),
),
],
);
await tester.pumpWidget(
buildTestRouterApp(
router: router,
dataService: data,
authService: auth,
),
);
await tester.pump();
expect(find.text('Loading dashboard data...'), findsOneWidget);
expect(find.byType(CircularProgressIndicator), findsWidgets);
});
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mileograph_flutter/components/pages/legs.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import '../helpers/fake_services.dart';
import '../helpers/test_app.dart';
import '../helpers/test_data.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('Entries page shows legs and mileage summary', (tester) async {
final data = FakeDataService()..legsValue = TestData.legs;
final auth = FakeAuthService(api: FakeApiService());
final distanceUnits = FakeDistanceUnitService(
unitOverride: DistanceUnit.milesDecimal,
);
await tester.pumpWidget(
buildTestApp(
child: const LegsPage(),
dataService: data,
authService: auth,
distanceUnitService: distanceUnits,
),
);
await tester.pumpAndSettle();
expect(find.text('Entries'), findsOneWidget);
expect(find.text('Page mileage'), findsOneWidget);
expect(find.textContaining('mi'), findsWidgets);
expect(find.text('London'), findsOneWidget);
expect(find.text('Oxford'), findsOneWidget);
});
testWidgets('Entries page shows empty state', (tester) async {
final data = FakeDataService()
..legsValue = []
..isLegsLoadingValue = false;
final auth = FakeAuthService(api: FakeApiService());
await tester.pumpWidget(
buildTestApp(
child: const LegsPage(),
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
expect(find.text('No entries found'), findsOneWidget);
expect(find.text('Adjust the filters or add a new leg.'), findsOneWidget);
});
}

View File

@@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mileograph_flutter/components/pages/traction.dart';
import '../helpers/fake_services.dart';
import '../helpers/test_app.dart';
import '../helpers/test_data.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('Traction page shows list data', (tester) async {
final data = FakeDataService()
..tractionValue = TestData.traction
..locoClassesValue = ['67', '68']
..eventFieldsValue = TestData.eventFields;
final auth = FakeAuthService(api: FakeApiService(), isElevatedValue: true);
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const TractionPage(),
),
],
);
await tester.pumpWidget(
buildTestRouterApp(
router: router,
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
expect(find.text('Traction'), findsOneWidget);
expect(find.text('67'), findsWidgets);
expect(find.text('001'), findsWidgets);
});
testWidgets('Traction page shows export when class filter set', (tester) async {
final data = FakeDataService()
..tractionValue = TestData.traction
..locoClassesValue = ['67', '68']
..eventFieldsValue = TestData.eventFields;
final auth = FakeAuthService(api: FakeApiService(), isElevatedValue: true);
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const TractionPage(),
),
],
);
await tester.pumpWidget(
buildTestRouterApp(
router: router,
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
expect(find.textContaining('Export'), findsNothing);
final classField = find.byWidgetPredicate(
(widget) =>
widget is TextField && widget.decoration?.labelText == 'Class',
);
await tester.enterText(classField, '67');
await tester.pump();
expect(find.text('Export 67'), findsOneWidget);
});
testWidgets('Traction page shows admin import option', (tester) async {
final data = FakeDataService()
..tractionValue = TestData.traction
..locoClassesValue = ['67', '68']
..eventFieldsValue = TestData.eventFields
..pendingLocoCountValue = 2;
final auth = FakeAuthService(api: FakeApiService(), isElevatedValue: true);
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const TractionPage(),
),
],
);
await tester.pumpWidget(
buildTestRouterApp(
router: router,
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
await tester.tap(find.byType(PopupMenuButton));
await tester.pumpAndSettle();
expect(find.text('Import traction'), findsOneWidget);
});
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mileograph_flutter/components/pages/trips.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import '../helpers/fake_services.dart';
import '../helpers/test_app.dart';
import '../helpers/test_data.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('Trips page shows trip details', (tester) async {
final data = FakeDataService()
..tripDetailsValue = TestData.tripDetails
..tripListValue = TestData.tripSummaries;
final auth = FakeAuthService(api: FakeApiService());
final distanceUnits = FakeDistanceUnitService(
unitOverride: DistanceUnit.milesDecimal,
);
await tester.pumpWidget(
buildTestApp(
child: const TripsPage(),
dataService: data,
authService: auth,
distanceUnitService: distanceUnits,
),
);
await tester.pumpAndSettle();
expect(find.text('Trips'), findsOneWidget);
expect(find.text('North Run'), findsOneWidget);
expect(find.textContaining('mi'), findsWidgets);
});
testWidgets('Trips page shows empty state', (tester) async {
final data = FakeDataService()
..tripDetailsValue = []
..tripListValue = []
..isTripDetailsLoadingValue = false;
final auth = FakeAuthService(api: FakeApiService());
await tester.pumpWidget(
buildTestApp(
child: const TripsPage(),
dataService: data,
authService: auth,
),
);
await tester.pumpAndSettle();
expect(find.text('No trips yet'), findsOneWidget);
expect(
find.text('Use the Add entry flow to start grouping legs into trips.'),
findsOneWidget,
);
});
}