Files
receipity/test/receipt_parser_test.dart
T
2026-09-03 15:09:17 +02:00

300 lines
6.2 KiB
Dart

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('does not treat CHIPS as payment keyword CHIP', () {
final items = ReceiptParser.parse('''
AH Protein
AH Chips
1,49
0.69
''');
expect(items, hasLength(2));
expect(items.map((item) => item.name), ['AH Protein', 'AH Chips']);
expect(items.map((item) => item.price), [1.49, 0.69]);
});
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
''';