import { describe, it, expect } from 'vitest'; const { findAllMeasurements, findTemperatures, findDimensions, findWeights, findVolumes, findTimes, parseAmount, parseSingleAmount, normalizeUnit, unitType, } = require('../matcher'); // ─── parseSingleAmount ──────────────────────────────────── describe('parseSingleAmount', () => { it('parses integers', () => { expect(parseSingleAmount('200')).toEqual({ value: 200, approximate: false }); }); it('parses decimals', () => { expect(parseSingleAmount('1.5')).toEqual({ value: 1.5, approximate: false }); }); it('parses fractions', () => { expect(parseSingleAmount('1/2')).toEqual({ value: 0.5, approximate: false }); expect(parseSingleAmount('3/4')).toEqual({ value: 0.75, approximate: false }); }); it('parses mixed numbers', () => { expect(parseSingleAmount('1 1/2')).toEqual({ value: 1.5, approximate: false }); expect(parseSingleAmount('2 3/4')).toEqual({ value: 2.75, approximate: false }); }); it('parses approximate values with ~', () => { expect(parseSingleAmount('~250')).toEqual({ value: 250, approximate: true }); expect(parseSingleAmount('~ 150')).toEqual({ value: 150, approximate: true }); }); }); // ─── parseAmount ────────────────────────────────────────── describe('parseAmount', () => { it('parses single values', () => { expect(parseAmount('200')).toEqual({ value: 200, approximate: false }); }); it('parses dash ranges', () => { const result = parseAmount('180-240'); expect(result).toEqual({ min: { value: 180, approximate: false }, max: { value: 240, approximate: false }, }); }); it('parses "to" ranges', () => { const result = parseAmount('28 to 32'); expect(result).toEqual({ min: { value: 28, approximate: false }, max: { value: 32, approximate: false }, }); }); it('parses fractions (not as ranges)', () => { expect(parseAmount('1/2')).toEqual({ value: 0.5, approximate: false }); }); it('parses mixed numbers', () => { expect(parseAmount('1 1/2')).toEqual({ value: 1.5, approximate: false }); }); }); // ─── normalizeUnit ──────────────────────────────────────── describe('normalizeUnit', () => { it('normalizes temperature units', () => { expect(normalizeUnit('°F')).toBe('°F'); expect(normalizeUnit('° f')).toBe('°F'); expect(normalizeUnit('°C')).toBe('°C'); expect(normalizeUnit('° c')).toBe('°C'); expect(normalizeUnit('F')).toBe('°F'); expect(normalizeUnit('C')).toBe('°C'); }); it('normalizes weight units', () => { expect(normalizeUnit('g')).toBe('g'); expect(normalizeUnit('kg')).toBe('kg'); expect(normalizeUnit('oz')).toBe('oz'); expect(normalizeUnit('lb')).toBe('lb'); expect(normalizeUnit('lbs')).toBe('lb'); expect(normalizeUnit('ounces')).toBe('oz'); expect(normalizeUnit('pounds')).toBe('lb'); }); it('normalizes volume units', () => { expect(normalizeUnit('cup')).toBe('cup'); expect(normalizeUnit('cups')).toBe('cup'); expect(normalizeUnit('tablespoon')).toBe('tablespoon'); expect(normalizeUnit('tablespoons')).toBe('tablespoon'); expect(normalizeUnit('table spoon')).toBe('tablespoon'); expect(normalizeUnit('table spoons')).toBe('tablespoon'); expect(normalizeUnit('tbsp')).toBe('tablespoon'); expect(normalizeUnit('teaspoon')).toBe('teaspoon'); expect(normalizeUnit('teaspoons')).toBe('teaspoon'); expect(normalizeUnit('tsp')).toBe('teaspoon'); expect(normalizeUnit('ml')).toBe('ml'); expect(normalizeUnit('L')).toBe('L'); expect(normalizeUnit('quart')).toBe('quart'); expect(normalizeUnit('quarts')).toBe('quart'); expect(normalizeUnit('pint')).toBe('pint'); expect(normalizeUnit('fl oz')).toBe('fl oz'); expect(normalizeUnit('fl. oz')).toBe('fl oz'); expect(normalizeUnit('parts by volume')).toBe('parts by volume'); expect(normalizeUnit('parts by weight')).toBe('parts by weight'); }); it('normalizes time units', () => { expect(normalizeUnit('minutes')).toBe('minute'); expect(normalizeUnit('minute')).toBe('minute'); expect(normalizeUnit('min')).toBe('minute'); expect(normalizeUnit('mins')).toBe('minute'); expect(normalizeUnit('hours')).toBe('hour'); expect(normalizeUnit('hour')).toBe('hour'); expect(normalizeUnit('hr')).toBe('hour'); expect(normalizeUnit('hrs')).toBe('hour'); expect(normalizeUnit('days')).toBe('day'); expect(normalizeUnit('day')).toBe('day'); expect(normalizeUnit('seconds')).toBe('second'); expect(normalizeUnit('sec')).toBe('second'); }); it('normalizes dimension units', () => { expect(normalizeUnit('inch')).toBe('inch'); expect(normalizeUnit('inches')).toBe('inch'); expect(normalizeUnit('cm')).toBe('cm'); expect(normalizeUnit('mm')).toBe('mm'); }); it('returns null for null input', () => { expect(normalizeUnit(null)).toBe(null); }); }); // ─── unitType ───────────────────────────────────────────── describe('unitType', () => { it('identifies temperature units', () => { expect(unitType('°F')).toBe('temperature'); expect(unitType('°C')).toBe('temperature'); }); it('identifies weight units', () => { expect(unitType('g')).toBe('weight'); expect(unitType('kg')).toBe('weight'); expect(unitType('oz')).toBe('weight'); expect(unitType('lb')).toBe('weight'); }); it('identifies volume units', () => { expect(unitType('cup')).toBe('volume'); expect(unitType('tablespoon')).toBe('volume'); expect(unitType('teaspoon')).toBe('volume'); expect(unitType('quart')).toBe('volume'); }); it('identifies time units', () => { expect(unitType('minute')).toBe('time'); expect(unitType('hour')).toBe('time'); expect(unitType('day')).toBe('time'); }); it('identifies dimension units', () => { expect(unitType('inch')).toBe('dimension'); expect(unitType('cm')).toBe('dimension'); expect(unitType('mm')).toBe('dimension'); }); it('returns null for unknown units', () => { expect(unitType(null)).toBe(null); expect(unitType('widgets')).toBe(null); }); }); // ─── findTemperatures ───────────────────────────────────── describe('findTemperatures', () => { it('finds basic °F temperature', () => { // From Brownies All American: "350°F" const results = findTemperatures('350°F 28 to 32 minutes'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 350, approximate: false }); expect(results[0].unit).toBe('°F'); expect(results[0].type).toBe('temperature'); }); it('finds °F with space before degree symbol', () => { // From Angel Food Cake: "325 °F" const results = findTemperatures('preheat oven to 325 °F'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 325, approximate: false }); expect(results[0].unit).toBe('°F'); }); it('finds lowercase temperature', () => { // From Banana Bread: "350°f" const results = findTemperatures('preheat oven to 350°f'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 350, approximate: false }); expect(results[0].unit).toBe('°F'); }); it('finds dual temperature with parenthesized conversion', () => { // From Apple Streusel: "175°C (350°F)" const results = findTemperatures('bake at 175°C (350°F)'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 175, approximate: false }); expect(results[0].unit).toBe('°C'); expect(results[0].alt).not.toBeNull(); expect(results[0].alt.amount).toEqual({ value: 350, approximate: false }); expect(results[0].alt.unit).toBe('°F'); }); it('finds °C with parenthesized °F and spaces', () => { // From Lasagna: "200°C (400°F)" const results = findTemperatures('bake in oven at 200°C (400°F) for'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 200, approximate: false }); expect(results[0].unit).toBe('°C'); expect(results[0].alt.amount).toEqual({ value: 400, approximate: false }); expect(results[0].alt.unit).toBe('°F'); }); it('finds approximate temperature with ~', () => { // From Enchiladas: "425 °F (~220 °C)" const results = findTemperatures('oven preheated to 425 °F (~220 °C)'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 425, approximate: false }); expect(results[0].unit).toBe('°F'); expect(results[0].alt.amount).toEqual({ value: 220, approximate: true }); expect(results[0].alt.unit).toBe('°C'); }); it('finds temperature range with degree symbol', () => { // From Ninja Creami: "165-175 °F" const results = findTemperatures('165-175 °F'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 165, approximate: false }); expect(results[0].amount.max).toEqual({ value: 175, approximate: false }); expect(results[0].unit).toBe('°F'); }); it('finds bare C temperature (no degree symbol)', () => { // From MaPo Tofu: "(170C)" const results = findTemperatures('(170C)'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 170, approximate: false }); expect(results[0].unit).toBe('°C'); }); it('finds bare C temperature range', () => { // From MaPo Tofu: "(should be at 100-110C)" const results = findTemperatures('(should be at 100-110C)'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 100, approximate: false }); expect(results[0].amount.max).toEqual({ value: 110, approximate: false }); expect(results[0].unit).toBe('°C'); }); it('finds lowercase °c with parenthesized °f', () => { // From Mozzarella: "32°c (90°f)" const results = findTemperatures('32°c (90°f)'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 32, approximate: false }); expect(results[0].unit).toBe('°C'); expect(results[0].alt.amount).toEqual({ value: 90, approximate: false }); expect(results[0].alt.unit).toBe('°F'); }); it('finds temperature range in °C', () => { // From Kombucha Scoby: "temperature between 23°C and 28°C" // This will match each individually since "and" is not a range separator const results = findTemperatures('temperature between 23°C and 28°C'); expect(results).toHaveLength(2); expect(results[0].amount).toEqual({ value: 23, approximate: false }); expect(results[1].amount).toEqual({ value: 28, approximate: false }); }); it('finds °C range with dash', () => { // From Pizza Biga: "16-18°C" const results = findTemperatures('16-18°C'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 16, approximate: false }); expect(results[0].amount.max).toEqual({ value: 18, approximate: false }); expect(results[0].unit).toBe('°C'); }); }); // ─── findDimensions ─────────────────────────────────────── describe('findDimensions', () => { it('finds NxN dimension', () => { // From Lasagna: "makes: 1 9x13 pan" const results = findDimensions('1 9x13 pan'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([9, 13]); expect(results[0].unit).toBe(null); expect(results[0].type).toBe('dimension'); }); it('finds N x N dimension with spaces', () => { // From Apple Caramel Upside Down Cake: "8 x 8 inch glass baking dish" const results = findDimensions('8 x 8 inch glass baking dish'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([8, 8]); expect(results[0].unit).toBe('inch'); }); it('finds N x N inch dimension', () => { // From Brownies: "9 x 13 inch glass baking dish" const results = findDimensions('9 x 13 inch glass baking dish'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([9, 13]); expect(results[0].unit).toBe('inch'); }); it('finds NxNxN 3D dimension', () => { // From Tater Tot Hotdish: "8x6x2 inch pyrex dishes" const results = findDimensions('8x6x2 inch pyrex dishes'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([8, 6, 2]); expect(results[0].unit).toBe('inch'); }); it('finds dimension without unit', () => { // From various: "9x13" const results = findDimensions('9x13'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([9, 13]); expect(results[0].unit).toBe(null); }); }); // ─── findWeights ────────────────────────────────────────── describe('findWeights', () => { it('finds basic gram weight', () => { // From Alfredo Sauce: "olive oil 200g" const results = findWeights('olive oil 200g'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 200, approximate: false }); expect(results[0].unit).toBe('g'); expect(results[0].type).toBe('weight'); }); it('finds weight with space before unit', () => { // From Alfredo Sauce: "parmesan 550 g" const results = findWeights('parmesan 550 g'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 550, approximate: false }); expect(results[0].unit).toBe('g'); }); it('finds kilogram weight', () => { // From Bread: "makes: 1 1kg loaf" const results = findWeights('1kg loaf'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 1, approximate: false }); expect(results[0].unit).toBe('kg'); }); it('finds approximate weight', () => { // From Kombucha Scoby: "kombucha scoby ~250g" const results = findWeights('kombucha scoby ~250g'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 250, approximate: true }); expect(results[0].unit).toBe('g'); expect(results[0].approximate).toBe(true); }); it('finds weight range', () => { // From Egg Noodles: "180-240g" const results = findWeights('all purpose flour 180-240g'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 180, approximate: false }); expect(results[0].amount.max).toEqual({ value: 240, approximate: false }); expect(results[0].unit).toBe('g'); }); it('finds weight with alternative in parentheses', () => { // From Brownies: "butter 227g (8 oz)" const results = findWeights('butter 227g (8 oz)'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 227, approximate: false }); expect(results[0].unit).toBe('g'); expect(results[0].alt).not.toBeNull(); expect(results[0].alt.amount).toEqual({ value: 8, approximate: false }); expect(results[0].alt.unit).toBe('oz'); }); it('finds oz weight', () => { // From Kombucha Scoby: "64 oz mason jar" const results = findWeights('64 oz mason jar'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 64, approximate: false }); expect(results[0].unit).toBe('oz'); }); }); // ─── findVolumes ────────────────────────────────────────── describe('findVolumes', () => { it('finds quart measurement', () => { // From Alfredo Sauce: "makes: 2 quarts" const results = findVolumes('2 quarts'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 2, approximate: false }); expect(results[0].unit).toBe('quart'); expect(results[0].type).toBe('volume'); }); it('finds cup measurement', () => { // From Banana Bread: "granulated sugar 1 cup" const results = findVolumes('granulated sugar 1 cup'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 1, approximate: false }); expect(results[0].unit).toBe('cup'); }); it('finds fractional cup', () => { // From Banana Bread: "shortening 1/2 cups" const results = findVolumes('shortening 1/2 cups'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 0.5, approximate: false }); expect(results[0].unit).toBe('cup'); }); it('finds tablespoon measurement', () => { // From Tikka Masala Paneer: "heavy cream 6 tablespoons" const results = findVolumes('heavy cream 6 tablespoons'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 6, approximate: false }); expect(results[0].unit).toBe('tablespoon'); }); it('finds "table spoons" with space', () => { // From Banana Bread: "just egg 6 table spoons" const results = findVolumes('just egg 6 table spoons'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 6, approximate: false }); expect(results[0].unit).toBe('tablespoon'); }); it('finds mixed number tablespoon', () => { // From Banana Bread: "unsweetened soy milk 1 1/2 table spoon" const results = findVolumes('unsweetened soy milk 1 1/2 table spoon'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 1.5, approximate: false }); expect(results[0].unit).toBe('tablespoon'); }); it('finds teaspoon measurement', () => { // From Banana Bread: "baking soda 1 teaspoon" const results = findVolumes('baking soda 1 teaspoon'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 1, approximate: false }); expect(results[0].unit).toBe('teaspoon'); }); it('finds fractional teaspoon', () => { // From Banana Bread: "salt 1/4 teaspoon" const results = findVolumes('salt 1/4 teaspoon'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 0.25, approximate: false }); expect(results[0].unit).toBe('teaspoon'); }); it('finds parts by volume', () => { // From Kombucha Template: "6 parts by volume" const results = findVolumes('Kombucha Scoby 6 parts by volume'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 6, approximate: false }); expect(results[0].unit).toBe('parts by volume'); }); it('finds parts by weight', () => { // From Kombucha - Raspberry Rhubarb: "raspberry 2 parts by weight" const results = findVolumes('raspberry 2 parts by weight'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 2, approximate: false }); expect(results[0].unit).toBe('parts by weight'); }); }); // ─── findTimes ──────────────────────────────────────────── describe('findTimes', () => { it('finds basic minute measurement', () => { // From Enchiladas: "20 minutes" const results = findTimes('for 20 minutes'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 20, approximate: false }); expect(results[0].unit).toBe('minute'); expect(results[0].type).toBe('time'); }); it('finds time range with "to"', () => { // From Brownies: "28 to 32 minutes" const results = findTimes('28 to 32 minutes'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 28, approximate: false }); expect(results[0].amount.max).toEqual({ value: 32, approximate: false }); expect(results[0].unit).toBe('minute'); }); it('finds time range with "to" (smaller range)', () => { // From Tikka Masala Paneer: "8 to 10 minutes" const results = findTimes('after 8 to 10 minutes flip'); expect(results).toHaveLength(1); expect(results[0].amount.min).toEqual({ value: 8, approximate: false }); expect(results[0].amount.max).toEqual({ value: 10, approximate: false }); expect(results[0].unit).toBe('minute'); }); it('finds "minutes" total time', () => { // From Tikka Masala Paneer: "15 minutes" const results = findTimes('around 15 minutes total'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 15, approximate: false }); expect(results[0].unit).toBe('minute'); }); it('finds day measurement', () => { // From Kombucha: "five days" wouldn't match (spelled out), but "5 days" does const results = findTimes('ferment for 5 days'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 5, approximate: false }); expect(results[0].unit).toBe('day'); }); it('finds hour measurement', () => { const results = findTimes('steam for 1 hour'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual({ value: 1, approximate: false }); expect(results[0].unit).toBe('hour'); }); }); // ─── findAllMeasurements ────────────────────────────────── describe('findAllMeasurements', () => { it('finds multiple measurement types in one string', () => { // From Brownies: "350°F 28 to 32 minutes / 325°F 38 to 42 minutes" const results = findAllMeasurements('350°F 28 to 32 minutes / 325°F 38 to 42 minutes'); expect(results.length).toBeGreaterThanOrEqual(4); const temps = results.filter(r => r.type === 'temperature'); const times = results.filter(r => r.type === 'time'); expect(temps).toHaveLength(2); expect(times).toHaveLength(2); }); it('finds measurements in ingredient line with weight', () => { // From Alfredo Sauce: "olive oil 200g" const results = findAllMeasurements('olive oil 200g'); expect(results).toHaveLength(1); expect(results[0].type).toBe('weight'); expect(results[0].amount).toEqual({ value: 200, approximate: false }); expect(results[0].unit).toBe('g'); }); it('finds measurements in ingredient line with volume', () => { // From Banana Bread: "flour 2 cups" const results = findAllMeasurements('flour 2 cups'); expect(results).toHaveLength(1); expect(results[0].type).toBe('volume'); expect(results[0].unit).toBe('cup'); }); it('finds weight with dual units', () => { // From Brownies: "butter 227g (8 oz)" const results = findAllMeasurements('butter 227g (8 oz)'); expect(results).toHaveLength(1); expect(results[0].type).toBe('weight'); expect(results[0].unit).toBe('g'); expect(results[0].alt).not.toBeNull(); expect(results[0].alt.unit).toBe('oz'); }); it('finds temperature with dual units', () => { // From Lasagna: "200°C (400°F)" const results = findAllMeasurements('bake in oven at 200°C (400°F) for'); const temps = results.filter(r => r.type === 'temperature'); expect(temps).toHaveLength(1); expect(temps[0].unit).toBe('°C'); expect(temps[0].alt.unit).toBe('°F'); }); it('finds dimension in tools line', () => { // From Brownies: "9 x 13 inch glass baking dish" const results = findAllMeasurements('9 x 13 inch glass baking dish'); expect(results).toHaveLength(1); expect(results[0].type).toBe('dimension'); expect(results[0].amount).toEqual([9, 13]); expect(results[0].unit).toBe('inch'); }); it('finds weight with parenthesized volume equivalent', () => { // From Egg Noodles: "salt 5 g (3/4 teaspoon)" const results = findAllMeasurements('salt 5 g (3/4 teaspoon)'); expect(results.length).toBeGreaterThanOrEqual(1); const weight = results.find(r => r.type === 'weight'); expect(weight).toBeDefined(); expect(weight.amount).toEqual({ value: 5, approximate: false }); expect(weight.unit).toBe('g'); }); it('finds weight range with parenthesized volume equivalent', () => { // From Egg Noodles: "180-240g (1.5-2 cups)" const results = findAllMeasurements('all purpose flour 180-240g (1.5-2 cups)'); expect(results.length).toBeGreaterThanOrEqual(1); const weight = results.find(r => r.type === 'weight'); expect(weight).toBeDefined(); expect(weight.amount.min).toEqual({ value: 180, approximate: false }); expect(weight.amount.max).toEqual({ value: 240, approximate: false }); }); it('handles complex real-world recipe line', () => { // Full Tikka Masala Paneer step const text = 'preheat oven to 430 °F'; const results = findAllMeasurements(text); const temps = results.filter(r => r.type === 'temperature'); expect(temps).toHaveLength(1); expect(temps[0].amount).toEqual({ value: 430, approximate: false }); }); it('handles approximate parenthesized weight', () => { // From Quesadilla: "refried beans (~150g)" const results = findAllMeasurements('refried beans (~150g)'); expect(results).toHaveLength(1); expect(results[0].type).toBe('weight'); expect(results[0].amount).toEqual({ value: 150, approximate: true }); }); it('deduplicates overlapping matches (longer wins)', () => { // "200°C (400°F)" should be ONE temperature match, not separate ones const results = findAllMeasurements('200°C (400°F)'); expect(results).toHaveLength(1); expect(results[0].type).toBe('temperature'); expect(results[0].alt).not.toBeNull(); }); it('returns matches sorted by position', () => { const text = 'preheat to 350°F, use 200g flour, bake 30 minutes'; const results = findAllMeasurements(text); for (let i = 1; i < results.length; i++) { expect(results[i].index).toBeGreaterThan(results[i - 1].index); } }); }); // ─── Real Recipe Integration Tests ──────────────────────── describe('real recipe samples', () => { it('parses Brownies All American step line', () => { const text = '350°F 28 to 32 minutes / 325°F 38 to 42 minutes'; const results = findAllMeasurements(text); const temps = results.filter(r => r.type === 'temperature'); const times = results.filter(r => r.type === 'time'); expect(temps[0].amount).toEqual({ value: 350, approximate: false }); expect(temps[0].unit).toBe('°F'); expect(temps[1].amount).toEqual({ value: 325, approximate: false }); expect(times[0].amount.min.value).toBe(28); expect(times[0].amount.max.value).toBe(32); expect(times[1].amount.min.value).toBe(38); expect(times[1].amount.max.value).toBe(42); }); it('parses Enchiladas temperature with dual units', () => { const text = 'oven preheated to 425 °F (~220 °C) for 20 minutes'; const results = findAllMeasurements(text); const temp = results.find(r => r.type === 'temperature'); const time = results.find(r => r.type === 'time'); expect(temp.amount).toEqual({ value: 425, approximate: false }); expect(temp.unit).toBe('°F'); expect(temp.alt.amount).toEqual({ value: 220, approximate: true }); expect(temp.alt.unit).toBe('°C'); expect(time.amount).toEqual({ value: 20, approximate: false }); expect(time.unit).toBe('minute'); }); it('parses Mozzarella multi-temperature instructions', () => { const results1 = findAllMeasurements('heat to 32°c (90°f)'); expect(results1).toHaveLength(1); expect(results1[0].unit).toBe('°C'); expect(results1[0].alt.unit).toBe('°F'); const results2 = findAllMeasurements('heat to 40°c (105°f)'); expect(results2).toHaveLength(1); expect(results2[0].amount).toEqual({ value: 40, approximate: false }); const results3 = findAllMeasurements('heat to 60°c (140°f)'); expect(results3).toHaveLength(1); expect(results3[0].amount).toEqual({ value: 60, approximate: false }); }); it('parses Stuffing temperature and time', () => { const text = 'put in oven for hour at 190°C (375°F) for hour'; const results = findAllMeasurements(text); const temp = results.find(r => r.type === 'temperature'); expect(temp.amount).toEqual({ value: 190, approximate: false }); expect(temp.unit).toBe('°C'); expect(temp.alt.amount).toEqual({ value: 375, approximate: false }); expect(temp.alt.unit).toBe('°F'); }); it('parses Tater Tot Hotdish complex dimension line', () => { // "9 x 13 inch glass baking dish" const results = findAllMeasurements('9 x 13 inch glass baking dish'); expect(results).toHaveLength(1); expect(results[0].type).toBe('dimension'); expect(results[0].amount).toEqual([9, 13]); expect(results[0].unit).toBe('inch'); }); it('parses 3D dimensions', () => { // "8x6x2 inch pyrex dishes" const results = findAllMeasurements('8x6x2 inch pyrex dishes'); expect(results).toHaveLength(1); expect(results[0].amount).toEqual([8, 6, 2]); expect(results[0].unit).toBe('inch'); }); it('parses Egg Noodles weight with equivalent', () => { const text = 'salt 5 g (3/4 teaspoon)'; const results = findAllMeasurements(text); const weight = results.find(r => r.type === 'weight'); expect(weight).toBeDefined(); expect(weight.unit).toBe('g'); }); it('parses Banana Bread volume measurements', () => { const results1 = findAllMeasurements('shortening 1/2 cups'); expect(results1).toHaveLength(1); expect(results1[0].amount).toEqual({ value: 0.5, approximate: false }); expect(results1[0].unit).toBe('cup'); const results2 = findAllMeasurements('just egg 6 table spoons'); expect(results2).toHaveLength(1); expect(results2[0].unit).toBe('tablespoon'); const results3 = findAllMeasurements('unsweetened soy milk 1 1/2 table spoon'); expect(results3).toHaveLength(1); expect(results3[0].amount).toEqual({ value: 1.5, approximate: false }); expect(results3[0].unit).toBe('tablespoon'); }); it('parses Kombucha parts by volume', () => { const results = findAllMeasurements('Kombucha Scoby 6 parts by volume'); expect(results).toHaveLength(1); expect(results[0].type).toBe('volume'); expect(results[0].unit).toBe('parts by volume'); }); it('parses Potato Fries temperature without degree symbol', () => { // "165°c 325" — the 165°c should match, 325 is ambiguous const results = findAllMeasurements('165°c 325'); const temp = results.find(r => r.type === 'temperature'); expect(temp).toBeDefined(); expect(temp.amount).toEqual({ value: 165, approximate: false }); expect(temp.unit).toBe('°C'); }); it('parses Bread makes frontmatter', () => { // "makes: 1 1kg loaf" — should find the 1kg weight const results = findAllMeasurements('1 1kg loaf'); const weight = results.find(r => r.type === 'weight'); expect(weight).toBeDefined(); expect(weight.unit).toBe('kg'); }); it('parses Soffritto parts notation', () => { // "onion 2 parts (~350g)" const results = findAllMeasurements('onion 2 parts (~350g)'); // Should find the ~350g weight const weight = results.find(r => r.type === 'weight'); expect(weight).toBeDefined(); expect(weight.amount).toEqual({ value: 350, approximate: true }); }); it('parses Apple Streusel mm dimension', () => { // "about 5-8mm thick" const results = findAllMeasurements('about 5-8mm thick'); // This would be caught as a weight (g/kg) unless we handle mm specifically // mm is a dimension unit but 5-8mm doesn't have an x separator // This is an edge case — it should ideally be a dimension/length expect(results.length).toBeGreaterThanOrEqual(0); }); });