import 'package:flutter_test/flutter_test.dart'; import 'package:fuel_tax_tracker/services/vin_parser.dart'; void main() { group('VinParser', () { test('parses a bare 17-character VIN', () { const text = ''' VEHICLE IDENTIFICATION NUMBER 1HGCM82633A004352 MFD BY HONDA MFG CO '''; expect(VinParser.parse(text), '1HGCM82633A004352'); }); test('prefers a "VIN:" labeled match over another candidate elsewhere', () { const text = ''' PART NO 4F2CZ58899BA12345 VIN: 1HGCM82633A004352 '''; expect(VinParser.parse(text), '1HGCM82633A004352'); }); test('normalizes to uppercase', () { expect(VinParser.parse('vin: 1hgcm82633a004352'), '1HGCM82633A004352'); }); test('normalizes an O to 0 — real VINs never contain one, so it reads as a misread zero', () { // Real OCR output from a Kia/Hyundai data plate photo: "0" (zero) // came back as "O" (letter) — this is exactly why I/O/Q are excluded // from real VINs in the first place (too easily confused with // 1/0/9), which cuts both ways for OCR reading one back off a plate. expect(VinParser.parse('V.I.N 5XYZKDAG2BGO50982 TYPE:MPY'), '5XYZKDAG2BG050982'); }); test('normalizes an I to 1 the same way', () { expect(VinParser.parse('VIN: 1HGCM82633AI04352'), '1HGCM82633A104352'); }); test('does not match a run containing Q — unlike I/O, not a plausible misread digit', () { expect(VinParser.parse('1HGCM82633AQ04352'), isNull); }); test('retries with whitespace stripped when OCR splits the VIN across a stray space', () { // Real OCR output from an etched-glass VIN photo: a space landed // mid-VIN (likely two separate detected text regions on the curved // surface), so no single unbroken 17-character run exists until the // whitespace is collapsed. expect(VinParser.parse('5XYZKD AG2BG050982'), '5XYZKDAG2BG050982'); }); test('a whitespace-stripped retry is only a last resort, after a normal match is preferred', () { const text = ''' PART NO 4F2CZ 58899BA12345 VIN: 1HGCM82633A004352 '''; // The "VIN:"-labeled match (unaffected by the space in the unrelated // part number above it) should win over anything the fallback might // have found in the stripped text. expect(VinParser.parse(text), '1HGCM82633A004352'); }); test('does not match a 16-character (too short) run', () { expect(VinParser.parse('1HGCM82633A00435'), isNull); }); test('does not match an 18-character (too long) run', () { expect(VinParser.parse('1HGCM82633A0043521'), isNull); }); test('returns null when nothing matches', () { expect(VinParser.parse('THANK YOU FOR YOUR PURCHASE'), isNull); }); test('matches a "V.I.N" label (period between each letter) — common on compliance/data plates', () { // Real text from a Kia/Hyundai-built data plate: "V.I.N" rather than // "VIN:" — a manufacturer plate styling the labeled pattern didn't // originally account for, so this fell through to relying on OCR // reading the bare 17-char run cleanly. const text = ''' THIS VEHICLE CONFORMS TO ALL APPLICABLE U.S.A. FEDERAL MOTOR VEHICLE SAFETY AND THEFT PREVENTION STANDARDS IN EFFECT ON THE DATE OF MANUFACTURE SHOWN ABOVE V.I.N 5XYZKDAG2BG050982 TYPE:MPV '''; expect(VinParser.parse(text), '5XYZKDAG2BG050982'); }); test('matches "V.I.N." with a trailing period too', () { expect(VinParser.parse('V.I.N. 5XYZKDAG2BG050982'), '5XYZKDAG2BG050982'); }); test('a "V.I.N" labeled match is still preferred over another candidate elsewhere', () { const text = ''' PART NO 4F2CZ58899BA12345 V.I.N 5XYZKDAG2BG050982 '''; expect(VinParser.parse(text), '5XYZKDAG2BG050982'); }); }); }