import { describe, it, expect, beforeEach } from 'vitest'; import initSqlJs from 'sql.js'; import type { Recipe } from '../types/recipe.js'; import { SearchIndex } from '../services/SearchIndex.js'; describe('SearchIndex', () => { let db: any; let searchIndex: SearchIndex; beforeEach(async () => { const SQL = await initSqlJs(); db = new SQL.Database(); db.exec(` CREATE TABLE recipes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, servings INTEGER, prep_time_minutes INTEGER, cook_time_minutes INTEGER, source_url TEXT, image_url TEXT, made INTEGER DEFAULT 0, rating INTEGER, notes TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ); CREATE TABLE ingredients ( id INTEGER PRIMARY KEY AUTOINCREMENT, recipe_id INTEGER NOT NULL, position INTEGER, quantity TEXT, unit TEXT, item TEXT NOT NULL, notes TEXT ); CREATE TABLE steps ( id INTEGER PRIMARY KEY AUTOINCREMENT, recipe_id INTEGER NOT NULL, position INTEGER, instruction TEXT NOT NULL ); CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ); CREATE TABLE recipe_tags ( recipe_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (recipe_id, tag_id) ); `); searchIndex = SearchIndex.getInstance(db); await searchIndex.initialize(); }); const insertRecipe = (overlay: Partial & { id?: number } = {}): Recipe => { const baseRecipe: Recipe = { id: 0, title: 'Test Recipe', description: 'A test description', servings: 2, prep_time_minutes: 10, cook_time_minutes: 20, source_url: null, image_url: null, made: false, rating: null, notes: null, created_at: Date.now(), updated_at: Date.now(), ingredients: [], steps: [], tags: [], }; return { ...baseRecipe, ...overlay, id: overlay.id ?? Date.now() }; }; it('should add and retrieve by search', () => { const recipe = insertRecipe({ title: 'Chocolate Cake' }); searchIndex.add(recipe); const ids = searchIndex.search('Chocolate'); expect(ids).toContain(recipe.id); }); it('should update index via update() method', () => { const recipe = insertRecipe({ title: 'Apple Pie' }); searchIndex.add(recipe); let ids = searchIndex.search('Apple'); expect(ids).toContain(recipe.id); const updated = { ...recipe, title: 'Cherry Pie' }; searchIndex.update(updated); ids = searchIndex.search('Apple'); expect(ids).not.toContain(recipe.id); ids = searchIndex.search('Cherry'); expect(ids).toContain(recipe.id); }); it('should remove from index', () => { const recipe = insertRecipe({ title: 'Banana Bread' }); searchIndex.add(recipe); searchIndex.remove(recipe.id); const ids = searchIndex.search('Banana'); expect(ids).not.toContain(recipe.id); }); it('should search across ingredients and tags', () => { const recipeId = Date.now(); const recipe = insertRecipe({ id: recipeId, title: 'Salad', ingredients: [{ id: 1, recipe_id: recipeId, position: 0, item: 'lettuce', notes: null, quantity: '', unit: '' }], tags: [{ id: 1, name: 'healthy' }], }); searchIndex.add(recipe); expect(searchIndex.search('lettuce')).toContain(recipe.id); expect(searchIndex.search('healthy')).toContain(recipe.id); }); });