import { describe, it, expect } from 'vitest'; import { parseIngredient } from '../../src/lib/server/parsers/ingredient'; describe('parseIngredient', () => { it.each([ ['200 g Mehl', { quantity: 200, unit: 'g', name: 'Mehl' }], ['1 kg Kartoffeln', { quantity: 1, unit: 'kg', name: 'Kartoffeln' }], ['500 ml Milch', { quantity: 500, unit: 'ml', name: 'Milch' }], ['1 TL Salz', { quantity: 1, unit: 'TL', name: 'Salz' }], ['2 EL Olivenöl', { quantity: 2, unit: 'EL', name: 'Olivenöl' }], ['3 Eier', { quantity: 3, unit: null, name: 'Eier' }], ['1/2 Zitrone', { quantity: 0.5, unit: null, name: 'Zitrone' }], ['1,5 l Wasser', { quantity: 1.5, unit: 'l', name: 'Wasser' }] ] as const)('parses %s', (input, expected) => { const parsed = parseIngredient(input); expect(parsed.quantity).toBe(expected.quantity); expect(parsed.unit).toBe(expected.unit); expect(parsed.name).toBe(expected.name); expect(parsed.raw_text).toBe(input); }); it('handles notes', () => { const p = parseIngredient('200 g Mehl (Type 550)'); expect(p.quantity).toBe(200); expect(p.name).toBe('Mehl'); expect(p.note).toBe('Type 550'); }); it('falls back to raw_text when unparsable', () => { const p = parseIngredient('etwas frischer Pfeffer'); expect(p.quantity).toBeNull(); expect(p.unit).toBeNull(); expect(p.name).toBe('etwas frischer Pfeffer'); expect(p.raw_text).toBe('etwas frischer Pfeffer'); }); it('handles ranges by taking the lower bound', () => { const p = parseIngredient('2-3 Tomaten'); expect(p.quantity).toBe(2); expect(p.name).toBe('Tomaten'); }); describe('Unicode-Bruchzeichen', () => { it.each([ ['½ TL Salz', 0.5, 'TL', 'Salz'], ['¼ kg Zucker', 0.25, 'kg', 'Zucker'], ['¾ l Milch', 0.75, 'l', 'Milch'], ['⅓ Tasse Mehl', 1 / 3, 'Tasse', 'Mehl'], ['⅔ TL Pfeffer', 2 / 3, 'TL', 'Pfeffer'], ['⅛ TL Muskat', 0.125, 'TL', 'Muskat'] ] as const)('%s', (input, qty, unit, name) => { const p = parseIngredient(input); expect(p.quantity).toBeCloseTo(qty, 5); expect(p.unit).toBe(unit); expect(p.name).toBe(name); }); it('Unicode-Bruch ohne Unit', () => { const p = parseIngredient('½ Zitrone'); expect(p.quantity).toBeCloseTo(0.5, 5); expect(p.unit).toBe(null); expect(p.name).toBe('Zitrone'); }); }); describe('Mengen-Plausibilitaet (Bounds)', () => { it('weist 0 als Menge ab → quantity null', () => { const p = parseIngredient('0 g Mehl'); expect(p.quantity).toBe(null); // name bleibt das was nach der "0" kommt — Importer muss das nicht // perfekt rekonstruieren, der raw_text bleibt erhalten. expect(p.raw_text).toBe('0 g Mehl'); }); it('weist negative Menge ab', () => { // "-1 EL Öl" — Minus führt regex direkt ins Fallback (kein \d am Start), // also bleibt name = full text. const p = parseIngredient('-1 EL Öl'); expect(p.quantity).toBe(null); }); it('weist Menge > 10000 ab', () => { const p = parseIngredient('99999 g Hokuspokus'); expect(p.quantity).toBe(null); }); it('akzeptiert die Obergrenze 10000 selbst', () => { const p = parseIngredient('10000 g Mehl'); expect(p.quantity).toBe(10000); }); it('akzeptiert führende Null bei Dezimalbrüchen', () => { const p = parseIngredient('0.5 kg Salz'); expect(p.quantity).toBe(0.5); expect(p.unit).toBe('kg'); }); it('akzeptiert deutsche führende Null', () => { const p = parseIngredient('0,25 l Wasser'); expect(p.quantity).toBe(0.25); expect(p.unit).toBe('l'); }); }); });