feat(home): „Alle Rezepte"-Sektion mit Sortierung und Endless-Scroll
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m21s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m21s
Neue Sektion unter „Zuletzt hinzugefügt": sortierbar nach Name, Bewertung, zuletzt gekocht und Hinzugefügt. Auswahl persistiert in localStorage (kochwas.allSort). - Neuer Endpoint GET /api/recipes/all?sort=name&limit=10&offset=0. - listAllRecipesPaginated(db, sort, limit, offset) im repository: NULLS-last-Emulation per CASE für rating/cooked — funktioniert auch auf älteren SQLite-Versionen. - Endless Scroll per IntersectionObserver auf ein Sentinel-Element am Listen-Ende (rootMargin 200px, damit schon vor dem harten Rand nachgeladen wird). Pagesize 10. - 4 neue Tests: Name-Sort, Rating-Sort, Cooked-Sort, Pagination-Offset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@ import { insertRecipe } from '../../src/lib/server/recipes/repository';
|
||||
import {
|
||||
searchLocal,
|
||||
listRecentRecipes,
|
||||
listAllRecipes
|
||||
listAllRecipes,
|
||||
listAllRecipesPaginated
|
||||
} from '../../src/lib/server/recipes/search-local';
|
||||
import type { Recipe } from '../../src/lib/types';
|
||||
|
||||
@@ -147,3 +148,49 @@ describe('listAllRecipes', () => {
|
||||
expect(all.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAllRecipesPaginated', () => {
|
||||
it('sorts by name asc case-insensitive', () => {
|
||||
const db = openInMemoryForTest();
|
||||
insertRecipe(db, recipe({ title: 'zucchini' }));
|
||||
insertRecipe(db, recipe({ title: 'Apfel' }));
|
||||
insertRecipe(db, recipe({ title: 'birnen' }));
|
||||
const page = listAllRecipesPaginated(db, 'name', 10, 0);
|
||||
expect(page.map((h) => h.title)).toEqual(['Apfel', 'birnen', 'zucchini']);
|
||||
});
|
||||
|
||||
it('paginates with limit + offset', () => {
|
||||
const db = openInMemoryForTest();
|
||||
for (let i = 0; i < 15; i++) insertRecipe(db, recipe({ title: `R${i.toString().padStart(2, '0')}` }));
|
||||
const first = listAllRecipesPaginated(db, 'name', 5, 0);
|
||||
const second = listAllRecipesPaginated(db, 'name', 5, 5);
|
||||
expect(first.length).toBe(5);
|
||||
expect(second.length).toBe(5);
|
||||
const overlap = first.filter((h) => second.some((s) => s.id === h.id));
|
||||
expect(overlap.length).toBe(0);
|
||||
});
|
||||
|
||||
it('sorts by rating desc, unrated last', () => {
|
||||
const db = openInMemoryForTest();
|
||||
const a = insertRecipe(db, recipe({ title: 'A' }));
|
||||
const b = insertRecipe(db, recipe({ title: 'B' }));
|
||||
const c = insertRecipe(db, recipe({ title: 'C' }));
|
||||
db.prepare('INSERT INTO profile(name) VALUES (?)').run('P');
|
||||
db.prepare('INSERT INTO rating(recipe_id, profile_id, stars) VALUES (?, 1, 3)').run(a);
|
||||
db.prepare('INSERT INTO rating(recipe_id, profile_id, stars) VALUES (?, 1, 5)').run(c);
|
||||
const page = listAllRecipesPaginated(db, 'rating', 10, 0);
|
||||
// C (5) > A (3) > B (null)
|
||||
expect(page.map((h) => h.title)).toEqual(['C', 'A', 'B']);
|
||||
});
|
||||
|
||||
it('sorts by last_cooked_at desc, never-cooked last', () => {
|
||||
const db = openInMemoryForTest();
|
||||
const a = insertRecipe(db, recipe({ title: 'A' }));
|
||||
const b = insertRecipe(db, recipe({ title: 'B' }));
|
||||
db.prepare('INSERT INTO profile(name) VALUES (?)').run('P');
|
||||
db.prepare('INSERT INTO cooking_log(recipe_id, profile_id) VALUES (?, 1)').run(a);
|
||||
const page = listAllRecipesPaginated(db, 'cooked', 10, 0);
|
||||
expect(page[0].title).toBe('A');
|
||||
expect(page[1].title).toBe('B');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user