From dac83c70ea4ba663631740c15843aecd6342646c Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Thu, 9 Apr 2026 10:02:47 +0200 Subject: [PATCH] feat(planner): DayMealCard gains onactionsheet prop for full-card mobile tap target Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/planner/DayMealCard.svelte | 100 +++++++++++-------- frontend/src/lib/planner/DayMealCard.test.ts | 34 +++++++ 2 files changed, 95 insertions(+), 39 deletions(-) diff --git a/frontend/src/lib/planner/DayMealCard.svelte b/frontend/src/lib/planner/DayMealCard.svelte index fb03bdf..26eae34 100644 --- a/frontend/src/lib/planner/DayMealCard.svelte +++ b/frontend/src/lib/planner/DayMealCard.svelte @@ -17,15 +17,19 @@ isToday = false, isSelected = false, readonly = false, - onaddrecipe + onaddrecipe, + onactionsheet }: { slot: Slot; isToday?: boolean; isSelected?: boolean; readonly?: boolean; onaddrecipe?: () => void; + onactionsheet?: () => void; } = $props(); + let actionSheetMode = $derived(!!onactionsheet && !!slot.recipe); + let metadata = $derived( [ slot.recipe?.cookTimeMin != null ? `${slot.recipe.cookTimeMin} Min` : null, @@ -44,49 +48,67 @@ ); -
- {#if slot.recipe} +{#if actionSheetMode} + +{:else} +
+ {#if slot.recipe} +

+ {slot.recipe.name} +

+ {#if metadata} +

{metadata}

+ {/if} - {#if !readonly} - + Jetzt kochen + + {#if onaddrecipe} + + {/if} +
+ {/if} + {:else} +

Kein Gericht geplant

+ {#if !readonly && onaddrecipe} + + {/if} {/if} - {:else} -

Kein Gericht geplant

- {#if !readonly && onaddrecipe} - - {/if} - {/if} -
+ +{/if} diff --git a/frontend/src/lib/planner/DayMealCard.test.ts b/frontend/src/lib/planner/DayMealCard.test.ts index cafa53a..c8f2ac0 100644 --- a/frontend/src/lib/planner/DayMealCard.test.ts +++ b/frontend/src/lib/planner/DayMealCard.test.ts @@ -81,4 +81,38 @@ describe('DayMealCard', () => { render(DayMealCard, { props: { slot: { id: 's2', slotDate: '2026-03-31', recipe: null }, isToday: false, readonly: false } }); expect(screen.queryByRole('button', { name: /Gericht hinzufügen/i })).toBeNull(); }); + + describe('onactionsheet prop (mobile full-card tap target)', () => { + it('card renders as a button when onactionsheet provided and recipe exists', () => { + render(DayMealCard, { props: { slot, onactionsheet: vi.fn() } }); + const card = screen.getByRole('button', { name: /Pasta Bolognese/i }); + expect(card).toBeTruthy(); + }); + + it('clicking card calls onactionsheet', async () => { + const onactionsheet = vi.fn(); + const user = userEvent.setup(); + render(DayMealCard, { props: { slot, onactionsheet } }); + await user.click(screen.getByRole('button', { name: /Pasta Bolognese/i })); + expect(onactionsheet).toHaveBeenCalledOnce(); + }); + + it('inline Jetzt kochen and Tauschen buttons are hidden when onactionsheet provided', () => { + render(DayMealCard, { props: { slot, onactionsheet: vi.fn() } }); + expect(screen.queryByRole('link', { name: /Jetzt kochen/i })).toBeNull(); + expect(screen.queryByRole('button', { name: /Tauschen/i })).toBeNull(); + }); + + it('falls back to normal rendering when onactionsheet not provided', () => { + render(DayMealCard, { props: { slot, readonly: false, onaddrecipe: vi.fn() } }); + expect(screen.queryByRole('button', { name: /Pasta Bolognese/i })).toBeNull(); + expect(screen.getByRole('link', { name: /Jetzt kochen/i })).toBeTruthy(); + }); + + it('empty slot does not render card as button even when onactionsheet provided', () => { + const emptySlot = { id: 's2', slotDate: '2026-03-31', recipe: null }; + render(DayMealCard, { props: { slot: emptySlot, onactionsheet: vi.fn(), onaddrecipe: vi.fn() } }); + expect(screen.queryByRole('button', { name: /Pasta Bolognese/i })).toBeNull(); + }); + }); });