From 8daaa0e21d183d263f24c6e94e7c0876ebaf5b51 Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Fri, 3 Apr 2026 09:27:43 +0200 Subject: [PATCH] fix(staples): pass ctx from URL through load function; fix script order in page Co-Authored-By: Claude Sonnet 4.6 --- .../routes/household/staples/+page.server.ts | 4 ++-- .../src/routes/household/staples/+page.svelte | 12 +++++----- .../household/staples/page.server.test.ts | 24 +++++++++++++++---- .../src/routes/household/staples/page.test.ts | 22 +++++++---------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/frontend/src/routes/household/staples/+page.server.ts b/frontend/src/routes/household/staples/+page.server.ts index 87e6b5a..0927f77 100644 --- a/frontend/src/routes/household/staples/+page.server.ts +++ b/frontend/src/routes/household/staples/+page.server.ts @@ -1,7 +1,7 @@ import type { PageServerLoad } from './$types'; import { apiClient } from '$lib/server/api'; -export const load: PageServerLoad = async ({ fetch }) => { +export const load: PageServerLoad = async ({ fetch, url }) => { const api = apiClient(fetch); const [categoriesResult, ingredientsResult] = await Promise.all([ @@ -24,5 +24,5 @@ export const load: PageServerLoad = async ({ fetch }) => { })) })); - return { categories }; + return { categories, ctx: url.searchParams.get('ctx') }; }; diff --git a/frontend/src/routes/household/staples/+page.svelte b/frontend/src/routes/household/staples/+page.svelte index 3aec917..aaa3615 100644 --- a/frontend/src/routes/household/staples/+page.svelte +++ b/frontend/src/routes/household/staples/+page.svelte @@ -1,18 +1,18 @@ - - Vorräte einrichten — Mealplan - - + + Vorräte einrichten — Mealplan + + {#if isOnboarding}
diff --git a/frontend/src/routes/household/staples/page.server.test.ts b/frontend/src/routes/household/staples/page.server.test.ts index 308ac2e..85af50a 100644 --- a/frontend/src/routes/household/staples/page.server.test.ts +++ b/frontend/src/routes/household/staples/page.server.test.ts @@ -41,9 +41,23 @@ describe('household staples page — load', () => { }); } + it('passes ctx from url searchParams into returned data', async () => { + mockApiResponses(); + const url = new URL('http://localhost/household/staples?ctx=onboarding'); + const result = await load({ fetch: vi.fn(), url } as any); + expect(result.ctx).toBe('onboarding'); + }); + + it('returns ctx as null when no ctx param is present', async () => { + mockApiResponses(); + const url = new URL('http://localhost/household/staples'); + const result = await load({ fetch: vi.fn(), url } as any); + expect(result.ctx).toBeNull(); + }); + it('fetches both categories and ingredients in parallel', async () => { mockApiResponses(); - await load({ fetch: vi.fn() } as any); + await load({ fetch: vi.fn(), url: new URL('http://localhost/household/staples') } as any); const calls = mockGet.mock.calls.map((c) => c[0]); expect(calls).toContain('/v1/ingredient-categories'); @@ -52,7 +66,7 @@ describe('household staples page — load', () => { it('groups ingredients by category id', async () => { mockApiResponses(); - const result = await load({ fetch: vi.fn() } as any); + const result = await load({ fetch: vi.fn(), url: new URL('http://localhost/household/staples') } as any); expect(result.categories).toHaveLength(2); const oele = result.categories.find((c: any) => c.id === 'cat-1'); @@ -62,7 +76,7 @@ describe('household staples page — load', () => { it('preserves isStaple flag on each ingredient', async () => { mockApiResponses(); - const result = await load({ fetch: vi.fn() } as any); + const result = await load({ fetch: vi.fn(), url: new URL('http://localhost/household/staples') } 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); @@ -79,7 +93,7 @@ describe('household staples page — load', () => { } }); - const result = await load({ fetch: vi.fn() } as any); + const result = await load({ fetch: vi.fn(), url: new URL('http://localhost/household/staples') } as any); const leer = result.categories.find((c: any) => c.id === 'cat-3'); expect(leer).toBeDefined(); expect(leer.ingredients).toHaveLength(0); @@ -87,7 +101,7 @@ describe('household staples page — load', () => { it('returns empty categories when API fails', async () => { mockGet.mockResolvedValue({ data: undefined, error: { status: 500 } }); - const result = await load({ fetch: vi.fn() } as any); + const result = await load({ fetch: vi.fn(), url: new URL('http://localhost/household/staples') } as any); expect(result.categories).toEqual([]); }); }); diff --git a/frontend/src/routes/household/staples/page.test.ts b/frontend/src/routes/household/staples/page.test.ts index 3f016b5..873bbc9 100644 --- a/frontend/src/routes/household/staples/page.test.ts +++ b/frontend/src/routes/household/staples/page.test.ts @@ -2,10 +2,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen } from '@testing-library/svelte'; import Page from './+page.svelte'; -vi.mock('$app/state', () => ({ - page: { url: { searchParams: { get: vi.fn() } } } -})); - const mockCategories = [ { id: 'cat-1', @@ -27,34 +23,34 @@ describe('staples page — onboarding context (?ctx=onboarding)', () => { }); it('renders ProgressSidebar with step 2 active', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); expect(screen.getByTestId('step-2')).toHaveAttribute('aria-current', 'step'); }); it('renders Continue button linking to /household/invite', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); const continueLink = screen.getByRole('link', { name: /weiter/i }); expect(continueLink).toHaveAttribute('href', '/household/invite'); }); it('renders Skip button linking to /planner', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); const skipLink = screen.getByRole('link', { name: /überspringen/i }); expect(skipLink).toHaveAttribute('href', '/planner'); }); it('renders the StaplesManager with categories', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); expect(screen.getByText('Öle & Fette')).toBeInTheDocument(); }); it('sets the page title', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); expect(document.title).toBe('Vorräte einrichten — Mealplan'); }); it('renders mobile step indicator Schritt 2 von 3', () => { - render(Page, { props: { data: { categories: mockCategories }, ctx: 'onboarding' } }); + render(Page, { props: { data: { categories: mockCategories, ctx: 'onboarding' } } }); expect(screen.getByText(/schritt 2 von 3/i)).toBeInTheDocument(); }); }); @@ -69,18 +65,18 @@ describe('staples page — settings context (no ctx)', () => { }); it('does not render ProgressSidebar', () => { - render(Page, { props: { data: { categories: mockCategories } } }); + render(Page, { props: { data: { categories: mockCategories, ctx: null } } }); expect(screen.queryByTestId('step-1')).not.toBeInTheDocument(); }); it('does not render Continue or Skip buttons', () => { - render(Page, { props: { data: { categories: mockCategories } } }); + render(Page, { props: { data: { categories: mockCategories, ctx: null } } }); expect(screen.queryByRole('link', { name: /weiter/i })).not.toBeInTheDocument(); expect(screen.queryByRole('link', { name: /überspringen/i })).not.toBeInTheDocument(); }); it('renders a settings heading', () => { - render(Page, { props: { data: { categories: mockCategories } } }); + render(Page, { props: { data: { categories: mockCategories, ctx: null } } }); expect(screen.getByRole('heading', { name: /vorräte/i })).toBeInTheDocument(); }); });