diff --git a/frontend/src/routes/household/staples/+page.server.ts b/frontend/src/routes/household/staples/+page.server.ts new file mode 100644 index 0000000..87e6b5a --- /dev/null +++ b/frontend/src/routes/household/staples/+page.server.ts @@ -0,0 +1,28 @@ +import type { PageServerLoad } from './$types'; +import { apiClient } from '$lib/server/api'; + +export const load: PageServerLoad = async ({ fetch }) => { + const api = apiClient(fetch); + + const [categoriesResult, ingredientsResult] = await Promise.all([ + api.GET('/v1/ingredient-categories'), + api.GET('/v1/ingredients') + ]); + + const rawCategories = categoriesResult.data ?? []; + const rawIngredients = ingredientsResult.data ?? []; + + const categories = rawCategories.map((cat) => ({ + id: cat.id!, + name: cat.name!, + ingredients: rawIngredients + .filter((ing) => ing.category?.id === cat.id) + .map((ing) => ({ + id: ing.id!, + name: ing.name!, + isStaple: ing.isStaple ?? false + })) + })); + + return { categories }; +}; diff --git a/frontend/src/routes/household/staples/page.server.test.ts b/frontend/src/routes/household/staples/page.server.test.ts new file mode 100644 index 0000000..6b28f30 --- /dev/null +++ b/frontend/src/routes/household/staples/page.server.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('$env/dynamic/private', () => ({ + env: { BACKEND_URL: 'http://localhost:8080' } +})); + +const mockGet = vi.fn(); +vi.mock('$lib/server/api', () => ({ + apiClient: () => ({ GET: mockGet }) +})); + +const mockCategories = [ + { id: 'cat-1', name: 'Öle & Fette' }, + { id: 'cat-2', name: 'Gewürze' } +]; + +const mockIngredients = [ + { id: 'ing-1', name: 'Olivenöl', isStaple: true, category: { id: 'cat-1', name: 'Öle & Fette' } }, + { id: 'ing-2', name: 'Butter', isStaple: false, category: { id: 'cat-1', name: 'Öle & Fette' } }, + { id: 'ing-3', name: 'Salz', isStaple: true, category: { id: 'cat-2', name: 'Gewürze' } } +]; + +describe('household staples page — load', () => { + let load: any; + + beforeEach(async () => { + mockGet.mockReset(); + vi.resetModules(); + const mod = await import('./+page.server'); + load = mod.load; + }); + + function mockApiResponses() { + mockGet.mockImplementation((path: string) => { + if (path === '/v1/ingredient-categories') { + return Promise.resolve({ data: mockCategories, error: undefined }); + } + if (path === '/v1/ingredients') { + return Promise.resolve({ data: mockIngredients, error: undefined }); + } + }); + } + + it('fetches both categories and ingredients in parallel', async () => { + mockApiResponses(); + await load({ fetch: vi.fn() } as any); + + const calls = mockGet.mock.calls.map((c) => c[0]); + expect(calls).toContain('/v1/ingredient-categories'); + expect(calls).toContain('/v1/ingredients'); + }); + + it('groups ingredients by category id', async () => { + mockApiResponses(); + const result = await load({ fetch: vi.fn() } as any); + + expect(result.categories).toHaveLength(2); + const oele = result.categories.find((c: any) => c.id === 'cat-1'); + expect(oele.ingredients).toHaveLength(2); + expect(oele.ingredients[0].name).toBe('Olivenöl'); + }); + + it('preserves isStaple flag on each ingredient', async () => { + mockApiResponses(); + const result = await load({ fetch: vi.fn() } as any); + + const oele = result.categories.find((c: any) => c.id === 'cat-1'); + expect(oele.ingredients.find((i: any) => i.name === 'Olivenöl').isStaple).toBe(true); + expect(oele.ingredients.find((i: any) => i.name === 'Butter').isStaple).toBe(false); + }); + + it('categories without ingredients are included with empty array', async () => { + mockGet.mockImplementation((path: string) => { + if (path === '/v1/ingredient-categories') { + return Promise.resolve({ data: [...mockCategories, { id: 'cat-3', name: 'Leer' }], error: undefined }); + } + if (path === '/v1/ingredients') { + return Promise.resolve({ data: mockIngredients, error: undefined }); + } + }); + + const result = await load({ fetch: vi.fn() } as any); + const leer = result.categories.find((c: any) => c.id === 'cat-3'); + expect(leer).toBeDefined(); + expect(leer.ingredients).toHaveLength(0); + }); +});