feat(search): sort=viewed in listAllRecipesPaginated
Neuer Sort 'viewed' macht LEFT JOIN gegen recipe_view, ordert nach last_viewed_at DESC mit alphabetischem Tiebreaker. NULL-Recipes (nie angesehen) landen alphabetisch sortiert hinter den angesehenen (CASE-NULL-last statt SQLite 3.30+ NULLS LAST). Ohne profileId faellt der Sort auf alphabetisch zurueck — Sort-Chip bleibt klickbar, ergibt aber sinnvolles Default-Verhalten ohne aktiviertes Profil. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import { openInMemoryForTest } from '../../src/lib/server/db';
|
||||
import { recordView, listViews } from '../../src/lib/server/recipes/views';
|
||||
import { createProfile } from '../../src/lib/server/profiles/repository';
|
||||
import { listAllRecipesPaginated } from '../../src/lib/server/recipes/search-local';
|
||||
|
||||
function seedRecipe(db: ReturnType<typeof openInMemoryForTest>, title: string): number {
|
||||
const r = db
|
||||
@@ -82,3 +83,41 @@ describe('recordView', () => {
|
||||
expect(() => recordView(db, profile.id, 999)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("listAllRecipesPaginated sort='viewed'", () => {
|
||||
it('puts recently-viewed recipes first, NULLs alphabetically last', async () => {
|
||||
const db = openInMemoryForTest();
|
||||
const profile = createProfile(db, 'Test');
|
||||
const recipeA = seedRecipe(db, 'Apfelkuchen');
|
||||
const recipeB = seedRecipe(db, 'Brokkoli');
|
||||
const recipeC = seedRecipe(db, 'Couscous');
|
||||
|
||||
// View order: B then A. C never viewed.
|
||||
recordView(db, profile.id, recipeB);
|
||||
await new Promise((r) => setTimeout(r, 1100));
|
||||
recordView(db, profile.id, recipeA);
|
||||
|
||||
const hits = listAllRecipesPaginated(db, 'viewed', 50, 0, profile.id);
|
||||
expect(hits.map((h) => h.id)).toEqual([recipeA, recipeB, recipeC]);
|
||||
});
|
||||
|
||||
it('falls back to alphabetical when profileId is null', () => {
|
||||
const db = openInMemoryForTest();
|
||||
seedRecipe(db, 'Couscous');
|
||||
seedRecipe(db, 'Apfelkuchen');
|
||||
seedRecipe(db, 'Brokkoli');
|
||||
|
||||
const hits = listAllRecipesPaginated(db, 'viewed', 50, 0, null);
|
||||
expect(hits.map((h) => h.title)).toEqual(['Apfelkuchen', 'Brokkoli', 'Couscous']);
|
||||
});
|
||||
|
||||
it('keeps existing sorts working unchanged', () => {
|
||||
const db = openInMemoryForTest();
|
||||
seedRecipe(db, 'Couscous');
|
||||
seedRecipe(db, 'Apfelkuchen');
|
||||
seedRecipe(db, 'Brokkoli');
|
||||
|
||||
const hits = listAllRecipesPaginated(db, 'name', 50, 0);
|
||||
expect(hits.map((h) => h.title)).toEqual(['Apfelkuchen', 'Brokkoli', 'Couscous']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user