MotorSignal-App/test/maintenance_tracking_test.dart

162 lines
5.8 KiB
Dart

import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:motorcycle_dash/models/maintenance_models.dart';
void main() {
group('Motorcycle Maintenance Due Tracking (MOB-16)', () {
test('MaintenanceData parses from specificationJson safely', () {
final initialSpec = json.encode({
'year': 2024,
'manufacturer': 'Yamaha',
'model': 'YZF-R7',
'baseOdometer': 1200.0,
'serviceRecords': [
{
'id': 'srv_1',
'date': '2026-08-15T10:00:00Z',
'odometer': 600.0,
'serviceType': 'Break-in Oil & Filter',
'notes': 'Yamalube 10W-40, OEM filter',
'cost': 45.00,
},
],
});
final data = MaintenanceData.fromSpecificationJson(initialSpec);
expect(data.baseOdometer, 1200.0);
expect(data.serviceRecords.length, 1);
expect(data.serviceRecords.first.serviceType, 'Break-in Oil & Filter');
expect(data.serviceRecords.first.cost, 45.00);
});
test('MaintenanceData handles null or empty specificationJson gracefully', () {
final dataNull = MaintenanceData.fromSpecificationJson(null);
expect(dataNull.baseOdometer, 0.0);
expect(dataNull.serviceRecords, isEmpty);
final dataEmpty = MaintenanceData.fromSpecificationJson('');
expect(dataEmpty.baseOdometer, 0.0);
expect(dataEmpty.serviceRecords, isEmpty);
final dataCorrupt = MaintenanceData.fromSpecificationJson('{corrupted json}');
expect(dataCorrupt.baseOdometer, 0.0);
expect(dataCorrupt.serviceRecords, isEmpty);
});
test('calculatePills accurately counts down miles and determines urgency', () {
final data = MaintenanceData(
baseOdometer: 1000.0,
serviceRecords: [
ServiceRecord(
id: 'srv_1',
date: DateTime(2026, 8, 1),
odometer: 1000.0,
serviceType: 'Engine Oil Change',
),
ServiceRecord(
id: 'srv_2',
date: DateTime(2026, 8, 10),
odometer: 1400.0,
serviceType: 'Chain Clean & Lube',
),
],
);
// Ride 500 miles
final pills = data.calculatePills(500.0);
// Total odometer = 1000 + 500 = 1500 mi.
// Oil last serviced at 1000 mi -> miles since service = 500 mi.
// Oil interval is 3000 mi -> miles remaining = 2500 mi (Urgency: ok).
final oilPill = pills.firstWhere((p) => p.key == 'oil');
expect(oilPill.milesRemaining, 2500.0);
expect(oilPill.urgency, MaintenanceUrgency.ok);
expect(oilPill.label, '2500 mi remaining');
// Chain last serviced at 1400 mi -> miles since service = 100 mi.
// Chain interval is 600 mi -> miles remaining = 500 mi (Urgency: ok).
final chainPill = pills.firstWhere((p) => p.key == 'chain');
expect(chainPill.milesRemaining, 500.0);
expect(chainPill.urgency, MaintenanceUrgency.ok);
// Tire wear: no service logged yet -> defaults to baseOdometer (1000 mi).
// Miles since service = 500 mi. Interval = 4000 mi -> remaining = 3500 mi.
final tirePill = pills.firstWhere((p) => p.key == 'tire');
expect(tirePill.milesRemaining, 3500.0);
expect(tirePill.urgency, MaintenanceUrgency.ok);
});
test('calculatePills flags dueSoon and overdue accurately', () {
final data = MaintenanceData(
baseOdometer: 2800.0,
serviceRecords: [
ServiceRecord(
id: 'srv_1',
date: DateTime(2026, 1, 1),
odometer: 0.0,
serviceType: 'Initial Oil',
),
ServiceRecord(
id: 'srv_2',
date: DateTime(2026, 1, 1),
odometer: 2250.0,
serviceType: 'Chain Service',
),
],
);
// Ride 50 miles -> total odometer = 2850 mi.
// Oil: last serviced 0 mi -> miles since service = 2850 mi.
// Interval = 3000 mi -> remaining = 150 mi (dueSoonThreshold is 350 mi) -> dueSoon!
final pills = data.calculatePills(50.0);
final oilPill = pills.firstWhere((p) => p.key == 'oil');
expect(oilPill.milesRemaining, 150.0);
expect(oilPill.urgency, MaintenanceUrgency.dueSoon);
expect(oilPill.label, 'Due Soon (150 mi)');
// Chain: last serviced at 2250 mi -> miles since service = 600 mi.
// Interval = 600 mi -> remaining = 0 mi -> overdue!
final chainPill = pills.firstWhere((p) => p.key == 'chain');
expect(chainPill.milesRemaining, 0.0);
expect(chainPill.urgency, MaintenanceUrgency.overdue);
expect(chainPill.label, contains('OVERDUE'));
});
test('updateSpecJsonWithMaintenance preserves existing spec data without mutations', () {
final original = json.encode({
'year': 2024,
'manufacturer': 'Ducati',
'model': 'Panigale V2',
'horsepower': 155.0,
'displacementCc': 955.0,
});
final updated = MaintenanceData.updateSpecJsonWithMaintenance(
originalSpecJson: original,
baseOdometer: 3400.0,
serviceRecords: [
ServiceRecord(
id: 'srv_1',
date: DateTime(2026, 9, 1),
odometer: 3000.0,
serviceType: 'Desmo Service',
cost: 850.0,
),
],
);
final decoded = json.decode(updated) as Map<String, dynamic>;
expect(decoded['year'], 2024);
expect(decoded['manufacturer'], 'Ducati');
expect(decoded['model'], 'Panigale V2');
expect(decoded['horsepower'], 155.0);
expect(decoded['displacementCc'], 955.0);
expect(decoded['baseOdometer'], 3400.0);
final records = decoded['serviceRecords'] as List;
expect(records.length, 1);
expect(records[0]['serviceType'], 'Desmo Service');
expect(records[0]['cost'], 850.0);
});
});
}