122 lines
3.6 KiB
TypeScript
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);
|
|
});
|
|
});
|