recipe-manager/src/backend/services/SearchIndex.test.ts

122 lines
3.6 KiB
TypeScript

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<Recipe> & { 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);
});
});