import 'package:flutter_test/flutter_test.dart'; import 'package:groci/models/ocr_element.dart'; import 'package:groci/services/receipt_parser.dart'; void main() { test('parses name and price at the end of a line', () { final items = ReceiptParser.parse(''' MILK 1.50 BREAD 2.00 TOTAL 3.50 CASH 5.00 CHANGE 1.50 '''); expect(items.map((item) => item.name), ['MILK', 'BREAD']); expect(items.map((item) => item.price), [1.50, 2.00]); }); test('parses name on one line and price on the next', () { final items = ReceiptParser.parse(''' MILK 1,50 BREAD 2,00 Totaal 3,50 '''); expect(items, hasLength(2)); expect(items.map((item) => item.name), ['MILK', 'BREAD']); expect(items.map((item) => item.price), [1.50, 2.00]); }); test('pairs two name lines with two price lines from short column OCR', () { final items = ReceiptParser.parse(''' Jumbo MILK BREAD 1,50 2,00 Totaal 3,50 '''); expect(items, hasLength(2)); expect(items.map((item) => item.name), ['MILK', 'BREAD']); expect(items.map((item) => item.price), [1.50, 2.00]); }); test('uses the next line when quantity and total are split', () { final items = ReceiptParser.parse(''' EGGS 2 X 1.50 3.00 BUTTER 2.25 SUBTOTAL 5.25 '''); expect(items, hasLength(2)); expect(items[0].name, 'EGGS'); expect(items[0].price, 3.00); expect(items[0].quantity, 2); expect(items[0].unitPrice, 1.50); expect(items[1].name, 'BUTTER'); expect(items[1].price, 2.25); }); test('computes a total when the quantity line has no line total', () { final items = ReceiptParser.parse(''' BROODSTICKS OLIJF 4 X 1,25 '''); expect(items, hasLength(1)); expect(items.single.name, 'BROODSTICKS OLIJF'); expect(items.single.price, 5.00); expect(items.single.quantity, 4); }); test('folds a discount into the previous item', () { final result = ReceiptParser.parseReceipt(''' AARDBEIEN 7,98 ACTIE AARDBEIEN -2,00 Totaal 5,98 VISA 5,98 Totaal korting: -2,00 '''); expect(result.items.map((item) => item.name), ['AARDBEIEN']); expect(result.items.single.price, 5.98); expect(result.items.single.discount, -2.00); expect(result.items.single.originalPrice, 7.98); expect(result.validationPassed, isTrue); }); test('parses Kruidvat-style inline quantity', () { final items = ReceiptParser.parse(''' ZEEPTABLET NEUTRAL 2 X 3,99 PER STUK 7,98 KORTING NEUTRAL ZE.HP -1,00 Totaal 6,98 '''); expect(items, hasLength(1)); expect(items.single.name, 'ZEEPTABLET NEUTRAL'); expect(items.single.quantity, 2); expect(items.single.unitPrice, 3.99); expect(items.single.price, 6.98); }); test('parses Lidl-style price-reduction lines', () { final items = ReceiptParser.parse(''' MELK 1,09 B In prijs verlaagd -0,50 Totaal 0,59 '''); expect(items, hasLength(1)); expect(items.single.name, 'MELK'); expect(items.single.price, 0.59); }); test('ignores BTW breakdown rows', () { final items = ReceiptParser.parse(''' MELK 2,09 Totaal 2,09 BTW B 9% 0,17 C 21% 0,00 Bedr.Excl 1,92 Bedr.Incl 2,09 '''); expect(items.map((item) => item.name), ['MELK']); }); test('pairs a name column with a price column from OCR', () { final items = ReceiptParser.parse(_jumboColumnOcr); expect(items.map((item) => item.name), isNot(contains('OMSCHRIJVING'))); expect(items.map((item) => item.name), isNot(contains('Totaal'))); expect(items.map((item) => item.name), isNot(contains('ACTIE AARDBEIEN'))); expect(items.first.name, 'JUMBO BIO VOLLE MELK'); expect(items.first.price, 2.09); final eggs = items.firstWhere( (item) => item.name.startsWith('BIOLOGISCH EI'), ); expect(eggs.quantity, 2); expect(eggs.price, 5.58); final berries = items.firstWhere((item) => item.name == 'AARDBEIEN'); expect(berries.quantity, 2); expect(berries.price, 5.98); expect(items.last.name, 'JUMBO PREM LUIER M4'); expect(items.last.price, 6.20); expect(items, hasLength(26)); }); test('scan path uses column parser for Jumbo name-then-price OCR dump', () { const ocr = ''' BIO VASTKOKEND AARD 2 X 1,99 AARDBEIEN 2 X 3,99 ACTIE AARDBEIEN BROCCOLII PREI CITROENEN KOMKOMMER 2 X 0,95 ACTIE KOMKOMMER BOL KNOFLOOK BROODSTICKS OLIJF 4 X 1.25 3,98 7,98 -2,00 1,24 0,69 2,11 1,90 -0,21 1,79 5,00 '''; final result = ReceiptParser.parseReceiptFromOcr( OcrDocument( elements: const [], lines: const [], imageWidth: 800, imageHeight: 1200, flattenedText: ocr, ), ); expect(result.items, hasLength(8)); expect(result.items[0].name, 'BIO VASTKOKEND AARD'); expect(result.items[0].quantity, 2); expect(result.items[0].price, 3.98); expect(result.items[1].name, 'AARDBEIEN'); expect(result.items[1].price, 5.98); expect(result.items[1].discount, -2.00); expect(result.items[5].name, 'KOMKOMMER'); expect(result.items[5].price, 1.69); expect(result.items.last.name, 'BROODSTICKS OLIJF'); expect(result.items.last.quantity, 4); expect(result.items.last.price, 5.00); expect(result.items.every((item) => !item.needsManualReview), isTrue); }); } const _jumboColumnOcr = ''' OMSCHRIJVING JUMBO BIO VOLLE MELK JUMBO BIO BLOEMKOOLS JUMBO BIO ROM TOMAAT JUMBO BIO SIZ LINZEN JUMBO BIO SIZ CHAMP. JUMBO FRITO BIOLOGISCH EI 6 ST 2 X 2,79 KABELJAUW GROENE KR JUMBO BIO KIPDI JFILE JUMBO RASP MOZZAREL JUMBO KAASBL BELEGEN GALBAN MOZZAREL MAXI TORTELL DROGE HAM PATURATIN NATURAL PAPRIKA ROOD BIO COTTAGE CHEESE FP BIO JB PLAK BIO VASTKOKEND AARD 2 X 1,99 AARDBEIEN 2 X 3,99 ACTIE AARDBEIEN BROCCOLI PREI CITROENEN KOMKOMMER 2 X 0,95 ACTIE KOMKOMMER BOL KNOFLOOK BROODSTICKS OLIJF 4 X 1,25 Krui dernhof 26 1112 PS Diemen 0206904928 www.jumbo. com JUMBO PREM LUIER M4 Totaal Betaal d: VISA Totaal korting: BEDRAG IN € 2,09 2,39 2,89 2,89 2,39 0,87 5,58 9,54 4.74 2,49 3,89 2,77 2,49 2,59 1,09 0,99 4,12 3,98 7,98 -2,00 1,24 0,69 2,11 1,90 -0,21 1,79 5,00 6,20 82,99 82,99 -2,21 ''';