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,265 @@
import 'package:flutter/foundation.dart';
import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/api_service.dart';
import 'package:mileograph_flutter/services/authservice.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import 'test_data.dart';
class FakeApiService extends ApiService {
FakeApiService({String baseUrl = 'https://example.com'})
: super(baseUrl: baseUrl);
final Map<String, dynamic> getResponses = {};
final Map<String, dynamic> postResponses = {};
@override
Future<dynamic> get(String endpoint, {Map<String, String>? headers}) async {
return getResponses[endpoint];
}
@override
Future<dynamic> post(
String endpoint,
dynamic data, {
Map<String, String>? headers,
}) async {
return postResponses[endpoint] ?? {};
}
}
class FakeAuthService extends AuthService {
FakeAuthService({
required super.api,
this.userIdValue = 'user-123',
this.usernameValue = 'railfan',
this.fullNameValue = 'Alex Rider',
this.isElevatedValue = true,
this.isLoggedInValue = true,
});
final String? userIdValue;
final String? usernameValue;
final String? fullNameValue;
final bool isElevatedValue;
final bool isLoggedInValue;
@override
bool get isLoggedIn => isLoggedInValue;
@override
String? get userId => userIdValue;
@override
String? get username => usernameValue;
@override
String? get fullName => fullNameValue;
@override
bool get isElevated => isElevatedValue;
}
class FakeDataService extends DataService {
FakeDataService({ApiService? api})
: super(api: api ?? FakeApiService());
HomepageStats? homepageStatsValue;
bool isHomepageLoadingValue = false;
List<TripSummary> tripsValue = [];
List<Leg> onThisDayValue = [];
bool isOnThisDayLoadingValue = false;
List<ClassClearanceProgress> classClearanceProgressValue = [];
bool isClassClearanceProgressLoadingValue = false;
List<LocoSummary> tractionValue = [];
bool isTractionLoadingValue = false;
bool tractionHasMoreValue = false;
List<LocoChange> latestLocoChangesValue = [];
bool isLatestLocoChangesLoadingValue = false;
bool latestLocoChangesHasMoreValue = false;
List<Leg> legsValue = [];
bool isLegsLoadingValue = false;
bool legsHasMoreValue = false;
List<TripDetail> tripDetailsValue = [];
List<TripSummary> tripListValue = [];
bool isTripDetailsLoadingValue = false;
List<String> locoClassesValue = [];
List<EventField> eventFieldsValue = [];
bool isEventFieldsLoadingValue = false;
int pendingLocoCountValue = 0;
double currentYearMileageValue = 0;
@override
HomepageStats? get homepageStats => homepageStatsValue;
@override
bool get isHomepageLoading => isHomepageLoadingValue;
@override
List<TripSummary> get trips => tripsValue;
@override
List<Leg> get onThisDay => onThisDayValue;
@override
bool get isOnThisDayLoading => isOnThisDayLoadingValue;
@override
List<ClassClearanceProgress> get classClearanceProgress =>
classClearanceProgressValue;
@override
bool get isClassClearanceProgressLoading =>
isClassClearanceProgressLoadingValue;
@override
List<LocoSummary> get traction => tractionValue;
@override
bool get isTractionLoading => isTractionLoadingValue;
@override
bool get tractionHasMore => tractionHasMoreValue;
@override
List<LocoChange> get latestLocoChanges => latestLocoChangesValue;
@override
bool get isLatestLocoChangesLoading => isLatestLocoChangesLoadingValue;
@override
bool get latestLocoChangesHasMore => latestLocoChangesHasMoreValue;
@override
List<Leg> get legs => legsValue;
@override
bool get isLegsLoading => isLegsLoadingValue;
@override
bool get legsHasMore => legsHasMoreValue;
@override
List<TripDetail> get tripDetails => tripDetailsValue;
@override
List<TripSummary> get tripList => tripListValue;
@override
bool get isTripDetailsLoading => isTripDetailsLoadingValue;
@override
List<String> get locoClasses => locoClassesValue;
@override
List<EventField> get eventFields => eventFieldsValue;
@override
bool get isEventFieldsLoading => isEventFieldsLoadingValue;
@override
int get pendingLocoCount => pendingLocoCountValue;
@override
double getMileageForCurrentYear() => currentYearMileageValue;
@override
Future<void> fetchHomepageStats() async {}
@override
Future<void> fetchOnThisDay({DateTime? date}) async {}
@override
Future<void> fetchTripDetails() async {}
@override
Future<void> fetchHadTraction({int offset = 0, int limit = 100}) async {}
@override
Future<void> fetchLatestLocoChanges({
int limit = 100,
int offset = 0,
bool append = false,
}) async {}
@override
Future<void> fetchClassClearanceProgress({
int offset = 0,
int limit = 20,
bool append = false,
bool onlyIncomplete = false,
}) async {}
@override
Future<void> fetchLegs({
int offset = 0,
int limit = 100,
String sortBy = 'date',
int sortDirection = 0,
String? dateRangeStart,
String? dateRangeEnd,
bool append = false,
bool unallocatedOnly = false,
}) async {}
@override
Future<List<TripLocoStat>> fetchTripLocoStats(int tripId) async {
return const [];
}
@override
Future<void> fetchTraction({
bool hadOnly = false,
int offset = 0,
int limit = 100,
String? locoClass,
String? locoNumber,
bool mileageFirst = true,
bool append = false,
Map<String, dynamic>? filters,
}) async {}
@override
Future<List<String>> fetchClassList({bool force = false}) async {
return locoClassesValue;
}
@override
Future<void> fetchEventFields({bool force = false}) async {}
@override
Future<void> fetchPendingLocoCount() async {}
@override
Future<Map<String, dynamic>?> fetchClassStats(String locoClass) async {
return TestData.classStats;
}
@override
Future<List<LeaderboardEntry>> fetchClassLeaderboard(
String locoClass, {
bool friends = false,
}) async {
return TestData.classLeaderboard;
}
}
class FakeDistanceUnitService extends DistanceUnitService {
FakeDistanceUnitService({this.unitOverride});
final DistanceUnit? unitOverride;
@override
DistanceUnit get unit => unitOverride ?? super.unit;
@override
String format(
double miles, {
int decimals = 1,
bool includeUnit = true,
}) {
final formatter = DistanceFormatter(unitOverride ?? super.unit);
return formatter.format(miles, decimals: decimals, includeUnit: includeUnit);
}
}

View File

@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mileograph_flutter/services/authservice.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import 'package:provider/provider.dart';
Widget buildTestApp({
required Widget child,
required DataService dataService,
required AuthService authService,
DistanceUnitService? distanceUnitService,
}) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>.value(value: authService),
ChangeNotifierProvider<DataService>.value(value: dataService),
ChangeNotifierProvider<DistanceUnitService>.value(
value: distanceUnitService ?? DistanceUnitService(),
),
],
child: MaterialApp(home: child),
);
}
Widget buildTestRouterApp({
required GoRouter router,
required DataService dataService,
required AuthService authService,
DistanceUnitService? distanceUnitService,
}) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>.value(value: authService),
ChangeNotifierProvider<DataService>.value(value: dataService),
ChangeNotifierProvider<DistanceUnitService>.value(
value: distanceUnitService ?? DistanceUnitService(),
),
],
child: MaterialApp.router(routerConfig: router),
);
}

328
test/helpers/test_data.dart Normal file
View File

@@ -0,0 +1,328 @@
import 'package:mileograph_flutter/objects/objects.dart';
class TestData {
static final user = UserData(
username: 'railfan',
fullName: 'Alex Rider',
userId: 'user-123',
email: 'alex@example.com',
entriesVisibility: 'public',
mileageVisibility: 'public',
elevated: true,
disabled: false,
);
static final topLocos = [
LocoSummary(
locoId: 100,
locoType: 'D',
locoNumber: '001',
locoName: 'Atlas',
locoClass: '67',
locoOperator: 'TestRail',
mileage: 1200.5,
journeys: 18,
trips: 6,
status: 'Active',
domain: 'mainline',
owner: 'TestRail',
livery: 'Blue',
location: 'Depot',
),
LocoSummary(
locoId: 101,
locoType: 'D',
locoNumber: '002',
locoName: 'Orion',
locoClass: '68',
locoOperator: 'TestRail',
mileage: 980.2,
journeys: 12,
trips: 4,
status: 'Active',
domain: 'mainline',
owner: 'TestRail',
livery: 'Orange',
location: 'Central',
),
];
static final leaderboard = [
LeaderboardEntry(
userId: 'u1',
username: 'driver_one',
userFullName: 'Driver One',
mileage: 3456.7,
),
LeaderboardEntry(
userId: 'u2',
username: 'driver_two',
userFullName: 'Driver Two',
mileage: 2999.1,
),
];
static final tripSummaries = [
TripSummary(
tripId: 1,
tripName: 'North Run',
tripMileage: 220.4,
legCount: 3,
locoStats: [
TripLocoStat(locoClass: '67', number: '001', won: true),
TripLocoStat(locoClass: '68', number: '002', won: false),
],
startDate: DateTime(2023, 10, 11),
endDate: DateTime(2023, 10, 13),
),
TripSummary(
tripId: 2,
tripName: 'Harbor Loop',
tripMileage: 85.6,
legCount: 1,
locoStats: const [],
startDate: DateTime(2023, 11, 5),
endDate: DateTime(2023, 11, 5),
),
];
static final tripDetails = [
TripDetail(
id: 1,
name: 'North Run',
mileage: 220.4,
legCount: 3,
locoStats: [
TripLocoStat(locoClass: '67', number: '001', won: true),
],
legs: [
TripLeg(
id: 10,
start: 'London',
end: 'Leeds',
beginTime: DateTime(2023, 10, 11, 9, 15),
network: 'National',
route: 'London → Leeds',
mileage: 185.2,
notes: 'Clear run',
locos: [
Loco(
id: 100,
type: 'D',
number: '001',
name: 'Atlas',
locoClass: '67',
operator: 'TestRail',
notes: '',
evn: '',
),
],
),
],
),
];
static final legs = [
Leg(
id: 501,
tripId: 1,
start: 'London',
end: 'Oxford',
beginTime: DateTime(2023, 9, 12, 7, 45),
timezone: 0,
network: 'National',
route: ['London', 'Oxford'],
mileage: 62.3,
notes: 'Morning service',
headcode: '1A00',
driving: 0,
user: 'railfan',
locos: [
Loco(
id: 100,
type: 'D',
number: '001',
name: 'Atlas',
locoClass: '67',
operator: 'TestRail',
notes: '',
evn: '',
),
],
),
Leg(
id: 502,
tripId: 2,
start: 'Oxford',
end: 'Bristol',
beginTime: DateTime(2023, 9, 13, 16, 10),
timezone: 0,
network: 'National',
route: ['Oxford', 'Bristol'],
mileage: 74.8,
notes: 'Evening run',
headcode: '1B10',
driving: 0,
user: 'railfan',
locos: [
Loco(
id: 101,
type: 'D',
number: '002',
name: 'Orion',
locoClass: '68',
operator: 'TestRail',
notes: '',
evn: '',
),
],
),
];
static List<Leg> onThisDayLegs() {
final pastYear = DateTime.now().year - 1;
return [
Leg(
id: 900,
tripId: 3,
start: 'York',
end: 'Durham',
beginTime: DateTime(pastYear, 7, 4, 10, 30),
timezone: 0,
network: 'National',
route: ['York', 'Durham'],
mileage: 60.0,
notes: 'Anniversary run',
headcode: '1C11',
driving: 0,
user: 'railfan',
locos: [
Loco(
id: 102,
type: 'D',
number: '003',
name: 'Nova',
locoClass: '66',
operator: 'TestRail',
notes: '',
evn: '',
),
],
),
];
}
static final latestLocoChanges = [
LocoChange(
locoId: 200,
locoClass: '67',
locoNumber: '001',
locoName: 'Atlas',
attrCode: 'loco_status',
attrDisplay: 'Status',
valueDisplay: 'Active',
validFrom: DateTime(2023, 10, 1),
approvedAt: DateTime(2023, 10, 2),
approvedBy: 'moderator',
),
];
static final classClearance = [
ClassClearanceProgress(
className: 'Class 67',
completed: 12,
total: 30,
percentComplete: 40.0,
activeCompleted: 8,
activeTotal: 20,
activePercent: 40.0,
),
];
static final traction = [
LocoSummary(
locoId: 100,
locoType: 'D',
locoNumber: '001',
locoName: 'Atlas',
locoClass: '67',
locoOperator: 'TestRail',
mileage: 1200.5,
journeys: 18,
trips: 6,
status: 'Active',
domain: 'mainline',
owner: 'TestRail',
livery: 'Blue',
location: 'Depot',
),
LocoSummary(
locoId: 101,
locoType: 'D',
locoNumber: '002',
locoName: 'Orion',
locoClass: '68',
locoOperator: 'TestRail',
mileage: 980.2,
journeys: 12,
trips: 4,
status: 'Active',
domain: 'mainline',
owner: 'TestRail',
livery: 'Orange',
location: 'Central',
),
];
static final eventFields = [
const EventField(name: 'power_unit', display: 'Power Unit', type: 'text'),
const EventField(
name: 'cab_air_conditioning',
display: 'Cab A/C',
enumValues: ['Yes', 'No'],
),
];
static final classStats = <String, dynamic>{
'loco_class': '67',
'total_mileage_with_class': 5500.5,
'avg_mileage_per_entry': 80.2,
'avg_mileage_per_loco_had': 420.0,
'had_count': 12,
'entries_with_class': 42,
'class_stats': {
'total': 30,
'status': [
{'status': 'Active', 'count': 18},
{'status': 'Stored', 'count': 12},
],
'domain': [
{'domain': 'mainline', 'count': 20},
{'domain': 'heritage', 'count': 10},
],
},
};
static final classLeaderboard = [
LeaderboardEntry(
userId: 'u3',
username: 'fan_three',
userFullName: 'Fan Three',
mileage: 1500.0,
),
];
static final homepageStats = HomepageStats(
totalMileage: 4523.8,
yearlyMileage: [
YearlyMileage(year: DateTime.now().year, mileage: 1200.5),
YearlyMileage(year: DateTime.now().year - 1, mileage: 990.2),
],
topLocos: topLocos,
leaderboard: leaderboard,
friendsLeaderboard: leaderboard,
trips: tripSummaries,
legCount: 27,
user: user,
);
}