diff --git a/frontend/src/lib/planner/VarietyWarningCards.test.ts b/frontend/src/lib/planner/VarietyWarningCards.test.ts index 4ae479e..5f65fde 100644 --- a/frontend/src/lib/planner/VarietyWarningCards.test.ts +++ b/frontend/src/lib/planner/VarietyWarningCards.test.ts @@ -3,30 +3,44 @@ import { render, screen } from '@testing-library/svelte'; import VarietyWarningCards from './VarietyWarningCards.svelte'; const warnings = [ - { title: 'Chicken zweimal diese Woche', explanation: 'Mo, Mi — erwäge einen Tausch.' }, - { title: 'Tomaten in 3 Gerichten', explanation: 'Mo, Di, Mi — sorge für Abwechslung.' } + { + title: 'Chicken zweimal diese Woche', + items: [ + { dayShort: 'Mo', recipeName: 'Chicken Tikka', slotId: 1 }, + { dayShort: 'Mi', recipeName: 'Chicken Curry', slotId: 3 } + ] + }, + { + title: 'Tomaten in 3 Gerichten', + items: [ + { dayShort: 'Mo', recipeName: 'Pasta Pomodoro', slotId: 1 }, + { dayShort: 'Di', recipeName: 'Tomatensuppe', slotId: 2 }, + { dayShort: 'Mi', recipeName: 'Pizza Margherita', slotId: 3 } + ] + } ]; describe('VarietyWarningCards', () => { it('renders one card per warning', () => { - render(VarietyWarningCards, { props: { warnings } }); + render(VarietyWarningCards, { props: { warnings, weekStart: '2026-04-07' } }); const cards = screen.getAllByTestId('warning-card'); expect(cards.length).toBe(2); }); it('renders warning titles', () => { - render(VarietyWarningCards, { props: { warnings } }); + render(VarietyWarningCards, { props: { warnings, weekStart: '2026-04-07' } }); expect(screen.getByText(/Chicken zweimal/)).toBeTruthy(); expect(screen.getByText(/Tomaten in 3/)).toBeTruthy(); }); it('renders warning explanations', () => { - render(VarietyWarningCards, { props: { warnings } }); - expect(screen.getByText(/erwäge einen Tausch/)).toBeTruthy(); + render(VarietyWarningCards, { props: { warnings, weekStart: '2026-04-07' } }); + expect(screen.getByText('Chicken Tikka')).toBeTruthy(); + expect(screen.getByText('Chicken Curry')).toBeTruthy(); }); it('renders nothing when warnings is empty', () => { - render(VarietyWarningCards, { props: { warnings: [] } }); + render(VarietyWarningCards, { props: { warnings: [], weekStart: '2026-04-07' } }); expect(screen.queryAllByTestId('warning-card').length).toBe(0); }); }); diff --git a/frontend/src/lib/recipes/RecipeForm.test.ts b/frontend/src/lib/recipes/RecipeForm.test.ts index 64cb585..7dcfc6d 100644 --- a/frontend/src/lib/recipes/RecipeForm.test.ts +++ b/frontend/src/lib/recipes/RecipeForm.test.ts @@ -29,7 +29,7 @@ const editProps = { name: 'Spaghetti Bolognese', serves: 4, cookTimeMin: 30, - effort: 'Medium', + effort: 'medium', heroImageUrl: undefined as string | undefined, ingredients: [ { name: 'Spaghetti', quantity: 200, unit: 'g' } diff --git a/frontend/src/routes/(app)/recipes/[id]/edit/+page.server.ts b/frontend/src/routes/(app)/recipes/[id]/edit/+page.server.ts index 89494c7..d430fd3 100644 --- a/frontend/src/routes/(app)/recipes/[id]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/recipes/[id]/edit/+page.server.ts @@ -77,10 +77,10 @@ export const actions: Actions = { effort, heroImageUrl, ingredients: (parsedIngredients as { name: string; quantity: string; unit: string }[]) - .filter((ing) => ing.name?.trim()) + .filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0) .map((ing, i) => ({ newIngredientName: ing.name.trim(), - quantity: Number(ing.quantity) || 0, + quantity: Number(ing.quantity), unit: ing.unit || '', sortOrder: i })), diff --git a/frontend/src/routes/(app)/recipes/[id]/edit/page.server.test.ts b/frontend/src/routes/(app)/recipes/[id]/edit/page.server.test.ts index 0195b1f..9594326 100644 --- a/frontend/src/routes/(app)/recipes/[id]/edit/page.server.test.ts +++ b/frontend/src/routes/(app)/recipes/[id]/edit/page.server.test.ts @@ -204,6 +204,25 @@ describe('edit recipe page — update action', () => { })); }); + it('filters out ingredients with quantity <= 0 before PUT', async () => { + mockPut.mockResolvedValue({ error: undefined }); + const fd = makeFormData({ + ingredientsJson: JSON.stringify([ + { name: 'Spaghetti', quantity: 200, unit: 'g' }, + { name: 'Salt', quantity: 0, unit: 'tsp' }, + { name: 'Pepper', quantity: -1, unit: 'tsp' } + ]) + }); + await actions.update({ + request: { formData: async () => fd }, + fetch: vi.fn(), + params: { id: 'r1' } + } as any).catch(() => {}); + const body = mockPut.mock.calls[0][1].body; + expect(body.ingredients).toHaveLength(1); + expect(body.ingredients[0].newIngredientName).toBe('Spaghetti'); + }); + it('returns fail(500) when API returns error', async () => { mockPut.mockResolvedValue({ error: { status: 500 } }); const result = await actions.update({ diff --git a/frontend/src/routes/(app)/recipes/new/+page.server.ts b/frontend/src/routes/(app)/recipes/new/+page.server.ts index 657e4cd..6f03c3e 100644 --- a/frontend/src/routes/(app)/recipes/new/+page.server.ts +++ b/frontend/src/routes/(app)/recipes/new/+page.server.ts @@ -49,10 +49,10 @@ export const actions: Actions = { effort, heroImageUrl, ingredients: (parsedIngredients as { name: string; quantity: string; unit: string }[]) - .filter((ing) => ing.name?.trim()) + .filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0) .map((ing, i) => ({ newIngredientName: ing.name.trim(), - quantity: Number(ing.quantity) || 0, + quantity: Number(ing.quantity), unit: ing.unit || '', sortOrder: i })), diff --git a/frontend/src/routes/(app)/recipes/new/page.server.test.ts b/frontend/src/routes/(app)/recipes/new/page.server.test.ts index 3734634..10625cc 100644 --- a/frontend/src/routes/(app)/recipes/new/page.server.test.ts +++ b/frontend/src/routes/(app)/recipes/new/page.server.test.ts @@ -163,6 +163,23 @@ describe('new recipe page — create action', () => { })); }); + it('filters out ingredients with quantity <= 0 before POST', async () => { + mockPost.mockResolvedValue({ error: undefined }); + const fd = makeFormData({ + ingredientsJson: JSON.stringify([ + { name: 'Spaghetti', quantity: 200, unit: 'g' }, + { name: 'Salt', quantity: 0, unit: 'tsp' }, + { name: 'Pepper', quantity: -1, unit: 'tsp' } + ]) + }); + await actions.create({ request: { formData: async () => fd }, fetch: vi.fn() } as any).catch( + () => {} + ); + const body = mockPost.mock.calls[0][1].body; + expect(body.ingredients).toHaveLength(1); + expect(body.ingredients[0].newIngredientName).toBe('Spaghetti'); + }); + it('returns fail(500) when API returns error', async () => { mockPost.mockResolvedValue({ error: { status: 500 } }); const result = await actions.create({ diff --git a/frontend/src/routes/(app)/recipes/page.test.ts b/frontend/src/routes/(app)/recipes/page.test.ts index 27e3ac9..37dffa7 100644 --- a/frontend/src/routes/(app)/recipes/page.test.ts +++ b/frontend/src/routes/(app)/recipes/page.test.ts @@ -5,9 +5,9 @@ import Page from './+page.svelte'; const mockData = { recipes: [ - { id: 'r1', name: 'Spaghetti Bolognese', cookTimeMin: 30, effort: 'Easy' }, - { id: 'r2', name: 'Chicken Curry', cookTimeMin: 45, effort: 'Medium' }, - { id: 'r3', name: 'Gemüsesuppe', cookTimeMin: 20, effort: 'Easy' } + { id: 'r1', name: 'Spaghetti Bolognese', cookTimeMin: 30, effort: 'easy' }, + { id: 'r2', name: 'Chicken Curry', cookTimeMin: 45, effort: 'medium' }, + { id: 'r3', name: 'Gemüsesuppe', cookTimeMin: 20, effort: 'easy' } ], activePlan: null };