From 2cebf504f24da198ca96a6c5ae20c30e33f72dc2 Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Fri, 10 Apr 2026 10:52:56 +0200 Subject: [PATCH] feat(planner): add RecipePickerDrawer slide-in drawer Wraps RecipePicker in a fixed right-side drawer with backdrop. Slide-in/out transition, backdrop click closes, purely presentational (open + onclose props from parent). Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/planner/RecipePickerDrawer.svelte | 77 ++++++++++++++++++ .../lib/planner/RecipePickerDrawer.test.ts | 80 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 frontend/src/lib/planner/RecipePickerDrawer.svelte create mode 100644 frontend/src/lib/planner/RecipePickerDrawer.test.ts diff --git a/frontend/src/lib/planner/RecipePickerDrawer.svelte b/frontend/src/lib/planner/RecipePickerDrawer.svelte new file mode 100644 index 0000000..3719288 --- /dev/null +++ b/frontend/src/lib/planner/RecipePickerDrawer.svelte @@ -0,0 +1,77 @@ + + + + + + +
+ +
+

+ Rezept wählen +

+ +
+ + +
+ +
+
diff --git a/frontend/src/lib/planner/RecipePickerDrawer.test.ts b/frontend/src/lib/planner/RecipePickerDrawer.test.ts new file mode 100644 index 0000000..2ba8733 --- /dev/null +++ b/frontend/src/lib/planner/RecipePickerDrawer.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import { userEvent } from '@testing-library/user-event'; +import RecipePickerDrawer from './RecipePickerDrawer.svelte'; + +const baseProps = { + open: true, + slotDate: '2026-04-14', + planId: 'plan-1', + suggestions: [], + allRecipes: [ + { id: 'r1', name: 'Pasta Bolognese', cookTimeMin: 45, effort: 'mittel' }, + { id: 'r2', name: 'Lachs', cookTimeMin: 20, effort: 'einfach' } + ], + isLoading: false, + onpick: vi.fn(), + onclose: vi.fn() +}; + +describe('RecipePickerDrawer', () => { + describe('visibility', () => { + it('renders drawer content when open=true', () => { + render(RecipePickerDrawer, { props: baseProps }); + expect(screen.getByTestId('recipe-picker-drawer')).toBeTruthy(); + }); + + it('drawer is not visible when open=false', () => { + render(RecipePickerDrawer, { props: { ...baseProps, open: false } }); + const drawer = screen.getByTestId('recipe-picker-drawer'); + // Drawer exists in DOM but should be off-screen / aria-hidden + expect(drawer.getAttribute('aria-hidden')).toBe('true'); + }); + + it('renders recipe list inside drawer', () => { + render(RecipePickerDrawer, { props: baseProps }); + expect(screen.getByText('Pasta Bolognese')).toBeTruthy(); + }); + }); + + describe('backdrop', () => { + it('renders backdrop when open', () => { + render(RecipePickerDrawer, { props: baseProps }); + expect(screen.getByTestId('drawer-backdrop')).toBeTruthy(); + }); + + it('calls onclose when backdrop is clicked', async () => { + const onclose = vi.fn(); + const user = userEvent.setup(); + render(RecipePickerDrawer, { props: { ...baseProps, onclose } }); + await user.click(screen.getByTestId('drawer-backdrop')); + expect(onclose).toHaveBeenCalledOnce(); + }); + }); + + describe('close button', () => { + it('renders a close button inside the drawer', () => { + render(RecipePickerDrawer, { props: baseProps }); + expect(screen.getByRole('button', { name: /schließen|close/i })).toBeTruthy(); + }); + + it('calls onclose when close button clicked', async () => { + const onclose = vi.fn(); + const user = userEvent.setup(); + render(RecipePickerDrawer, { props: { ...baseProps, onclose } }); + await user.click(screen.getByRole('button', { name: /schließen|close/i })); + expect(onclose).toHaveBeenCalledOnce(); + }); + }); + + describe('recipe picking', () => { + it('calls onpick when a recipe is selected', async () => { + const onpick = vi.fn(); + const user = userEvent.setup(); + render(RecipePickerDrawer, { props: { ...baseProps, onpick } }); + const pickButtons = screen.getAllByRole('button', { name: /Wählen/i }); + await user.click(pickButtons[0]); + expect(onpick).toHaveBeenCalledOnce(); + }); + }); +});