diff --git a/frontend/src/lib/recipes/IngredientList.test.ts b/frontend/src/lib/recipes/IngredientList.test.ts
index 927348a..ffc2c95 100644
--- a/frontend/src/lib/recipes/IngredientList.test.ts
+++ b/frontend/src/lib/recipes/IngredientList.test.ts
@@ -41,4 +41,17 @@ describe('IngredientList', () => {
render(IngredientList, { props: { ingredients: [] } });
expect(screen.getByText(/zutaten/i)).toBeInTheDocument();
});
+
+ it('renders ingredients sorted by sortOrder', () => {
+ const unsorted = [
+ { ingredientId: 'i3', name: 'Oregano', quantity: 1, unit: 'TL', sortOrder: 3 },
+ { ingredientId: 'i1', name: 'Spaghetti', quantity: 200, unit: 'g', sortOrder: 1 },
+ { ingredientId: 'i2', name: 'Hackfleisch', quantity: 400, unit: 'g', sortOrder: 2 }
+ ];
+ render(IngredientList, { props: { ingredients: unsorted } });
+ const spans = document.querySelectorAll('li span:last-child');
+ expect(spans[0].textContent).toBe('Spaghetti');
+ expect(spans[1].textContent).toBe('Hackfleisch');
+ expect(spans[2].textContent).toBe('Oregano');
+ });
});
diff --git a/frontend/src/lib/recipes/RecipeHero.svelte b/frontend/src/lib/recipes/RecipeHero.svelte
index 9674f6c..19c7144 100644
--- a/frontend/src/lib/recipes/RecipeHero.svelte
+++ b/frontend/src/lib/recipes/RecipeHero.svelte
@@ -1,17 +1,17 @@
@@ -25,6 +12,15 @@
+
+
diff --git a/frontend/src/routes/(app)/recipes/[id]/page.server.test.ts b/frontend/src/routes/(app)/recipes/[id]/page.server.test.ts
index a5dc7bb..8eeebd4 100644
--- a/frontend/src/routes/(app)/recipes/[id]/page.server.test.ts
+++ b/frontend/src/routes/(app)/recipes/[id]/page.server.test.ts
@@ -54,4 +54,13 @@ describe('recipe detail page — load', () => {
load({ fetch: vi.fn(), params: { id: 'nonexistent' } } as any)
).rejects.toMatchObject({ status: 404 });
});
+
+ it('throws 404 error when API returns 403 (different household — intentional)', async () => {
+ // Security design: we return 404 for both "not found" and "forbidden"
+ // to avoid revealing resource existence to unauthorized users
+ mockGet.mockResolvedValue({ data: undefined, error: { status: 403 } });
+ await expect(
+ load({ fetch: vi.fn(), params: { id: 'r-other-household' } } as any)
+ ).rejects.toMatchObject({ status: 404 });
+ });
});
diff --git a/frontend/src/routes/(app)/recipes/[id]/page.test.ts b/frontend/src/routes/(app)/recipes/[id]/page.test.ts
index f599700..c25139c 100644
--- a/frontend/src/routes/(app)/recipes/[id]/page.test.ts
+++ b/frontend/src/routes/(app)/recipes/[id]/page.test.ts
@@ -66,4 +66,27 @@ describe('recipe detail page', () => {
expect(screen.getByText('Wasser aufsetzen')).toBeInTheDocument();
expect(screen.getByText('Sauce zubereiten')).toBeInTheDocument();
});
+
+ it('renders edit link to /recipes/[id]/edit', () => {
+ render(Page, { props: { data: mockData } });
+ const editLink = screen.getByRole('link', { name: /bearbeiten/i });
+ expect(editLink).toHaveAttribute('href', '/recipes/r1/edit');
+ });
+
+ it('renders tag pills in hero', () => {
+ render(Page, { props: { data: mockData } });
+ expect(screen.getByText('Pasta')).toBeInTheDocument();
+ });
+
+ it('renders hero image when heroImageUrl is provided', () => {
+ render(Page, {
+ props: {
+ data: {
+ recipe: { ...mockData.recipe, heroImageUrl: '/uploads/pasta.jpg' }
+ }
+ }
+ });
+ const img = screen.getByRole('img', { name: /spaghetti bolognese/i });
+ expect(img).toHaveAttribute('src', '/uploads/pasta.jpg');
+ });
});