From 33cccd3d634aa59e2c678e65a78496663be14870 Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Fri, 10 Apr 2026 16:23:15 +0200 Subject: [PATCH] feat(settings): add +page.server.ts loading stapleCount, memberCount, userName Co-Authored-By: Claude Sonnet 4.6 --- .../src/routes/(app)/settings/+page.server.ts | 20 ++++ .../routes/(app)/settings/page.server.test.ts | 105 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 frontend/src/routes/(app)/settings/+page.server.ts create mode 100644 frontend/src/routes/(app)/settings/page.server.test.ts diff --git a/frontend/src/routes/(app)/settings/+page.server.ts b/frontend/src/routes/(app)/settings/+page.server.ts new file mode 100644 index 0000000..127f8c9 --- /dev/null +++ b/frontend/src/routes/(app)/settings/+page.server.ts @@ -0,0 +1,20 @@ +import type { PageServerLoad } from './$types'; +import { apiClient } from '$lib/server/api'; + +export const load: PageServerLoad = async ({ fetch, locals }) => { + const api = apiClient(fetch); + + const [ingredientsRes, householdRes] = await Promise.all([ + api.GET('/v1/ingredients'), + api.GET('/v1/households/mine') + ]); + + const stapleCount = ingredientsRes.data?.filter((i) => i.isStaple).length ?? 0; + const memberCount = householdRes.data?.data?.members?.length ?? 0; + + return { + stapleCount, + memberCount, + userName: locals.benutzer!.name + }; +}; diff --git a/frontend/src/routes/(app)/settings/page.server.test.ts b/frontend/src/routes/(app)/settings/page.server.test.ts new file mode 100644 index 0000000..788cd81 --- /dev/null +++ b/frontend/src/routes/(app)/settings/page.server.test.ts @@ -0,0 +1,105 @@ +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 mockIngredients = [ + { id: 'ing-1', name: 'Olivenöl', isStaple: true }, + { id: 'ing-2', name: 'Butter', isStaple: false }, + { id: 'ing-3', name: 'Salz', isStaple: true } +]; + +const mockHousehold = { + status: 'OK', + data: { + id: 'hh-1', + name: 'Familie Raddatz', + members: [ + { userId: 'u-1', name: 'Marcel' }, + { userId: 'u-2', name: 'Anna' }, + { userId: 'u-3', name: 'Ben' } + ] + } +}; + +const mockLocals = { benutzer: { id: 'u-1', name: 'Marcel Raddatz' } }; + +describe('settings 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/ingredients') { + return Promise.resolve({ data: mockIngredients, error: undefined }); + } + if (path === '/v1/households/mine') { + return Promise.resolve({ data: mockHousehold, error: undefined }); + } + }); + } + + it('returns stapleCount as number of ingredients where isStaple=true', async () => { + mockApiResponses(); + const result = await load({ fetch: vi.fn(), locals: mockLocals } as any); + expect(result.stapleCount).toBe(2); + }); + + it('returns memberCount as number of household members', async () => { + mockApiResponses(); + const result = await load({ fetch: vi.fn(), locals: mockLocals } as any); + expect(result.memberCount).toBe(3); + }); + + it('returns userName from locals.benutzer.name', async () => { + mockApiResponses(); + const result = await load({ fetch: vi.fn(), locals: mockLocals } as any); + expect(result.userName).toBe('Marcel Raddatz'); + }); + + it('fetches ingredients and household in parallel', async () => { + mockApiResponses(); + await load({ fetch: vi.fn(), locals: mockLocals } as any); + const calls = mockGet.mock.calls.map((c) => c[0]); + expect(calls).toContain('/v1/ingredients'); + expect(calls).toContain('/v1/households/mine'); + }); + + it('defaults stapleCount to 0 when ingredients API fails', async () => { + mockGet.mockImplementation((path: string) => { + if (path === '/v1/ingredients') { + return Promise.resolve({ data: undefined, error: { status: 500 } }); + } + if (path === '/v1/households/mine') { + return Promise.resolve({ data: mockHousehold, error: undefined }); + } + }); + const result = await load({ fetch: vi.fn(), locals: mockLocals } as any); + expect(result.stapleCount).toBe(0); + }); + + it('defaults memberCount to 0 when household API fails', async () => { + mockGet.mockImplementation((path: string) => { + if (path === '/v1/ingredients') { + return Promise.resolve({ data: mockIngredients, error: undefined }); + } + if (path === '/v1/households/mine') { + return Promise.resolve({ data: undefined, error: { status: 500 } }); + } + }); + const result = await load({ fetch: vi.fn(), locals: mockLocals } as any); + expect(result.memberCount).toBe(0); + }); +});