Files
crumbs-original/src/lib/server/db/search.test.ts
T
2026-03-05 21:16:45 +01:00

117 lines
3.2 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { createTestDb } from './test-helpers.js';
import { notes, tags, noteTags } from './schema.js';
import { and, eq, like, or } from 'drizzle-orm';
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import type * as schema from './schema.js';
let db: BetterSQLite3Database<typeof schema>;
beforeEach(async () => {
const testDb = createTestDb();
db = testDb.db;
const now = new Date();
await db.insert(notes).values([
{ id: 'n1', title: 'Shopping List', content: 'Buy milk and eggs', createdAt: now, updatedAt: now },
{ id: 'n2', title: 'Work Notes', content: 'Meeting at 3pm #work', createdAt: now, updatedAt: now },
{ id: 'n3', title: 'Recipe', content: 'Pasta with tomato sauce', createdAt: now, updatedAt: now },
{ id: 'n4', title: 'Trashed', content: 'Should not appear', trashed: true, createdAt: now, updatedAt: now }
]);
const [workTag] = await db.insert(tags).values({ name: 'work' }).returning();
await db.insert(noteTags).values({ noteId: 'n2', tagId: workTag.id });
});
describe('Search', () => {
it('should search by title', async () => {
const pattern = '%shopping%';
const results = await db
.select()
.from(notes)
.where(and(eq(notes.trashed, false), like(notes.title, pattern)));
expect(results).toHaveLength(1);
expect(results[0].id).toBe('n1');
});
it('should search by content', async () => {
const pattern = '%tomato%';
const results = await db
.select()
.from(notes)
.where(and(eq(notes.trashed, false), like(notes.content, pattern)));
expect(results).toHaveLength(1);
expect(results[0].id).toBe('n3');
});
it('should search across title and content', async () => {
const pattern = '%milk%';
const results = await db
.select()
.from(notes)
.where(
and(
eq(notes.trashed, false),
or(like(notes.title, pattern), like(notes.content, pattern))
)
);
expect(results).toHaveLength(1);
expect(results[0].id).toBe('n1');
});
it('should not include trashed notes', async () => {
const pattern = '%should not%';
const results = await db
.select()
.from(notes)
.where(
and(
eq(notes.trashed, false),
or(like(notes.title, pattern), like(notes.content, pattern))
)
);
expect(results).toHaveLength(0);
});
it('should return empty for no matches', async () => {
const pattern = '%nonexistent%';
const results = await db
.select()
.from(notes)
.where(
and(
eq(notes.trashed, false),
or(like(notes.title, pattern), like(notes.content, pattern))
)
);
expect(results).toHaveLength(0);
});
it('should be case-insensitive with LIKE', async () => {
const pattern = '%SHOPPING%';
const results = await db
.select()
.from(notes)
.where(and(eq(notes.trashed, false), like(notes.title, pattern)));
// SQLite LIKE is case-insensitive for ASCII by default
expect(results).toHaveLength(1);
});
it('should search by tag name', async () => {
const tagResults = await db
.select({ noteId: noteTags.noteId })
.from(noteTags)
.innerJoin(tags, eq(noteTags.tagId, tags.id))
.where(like(tags.name, '%work%'));
expect(tagResults).toHaveLength(1);
expect(tagResults[0].noteId).toBe('n2');
});
});