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