feat(recipes): add RecipeGrid with 2/4-col responsive grid and empty state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 09:44:05 +02:00
parent 35ed6ca878
commit a733e8dd66
2 changed files with 63 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import RecipeCard from './RecipeCard.svelte';
type RecipeSummary = { id: string; name: string; cookTimeMin?: number; effort?: string; heroImageUrl?: string };
let { recipes }: { recipes: RecipeSummary[] } = $props();
</script>
{#if recipes.length > 0}
<div data-testid="recipe-grid" class="grid grid-cols-2 lg:grid-cols-4 gap-[8px] lg:gap-[12px] p-[16px]">
{#each recipes as recipe (recipe.id)}
<RecipeCard {recipe} compact={true} />
{/each}
</div>
{:else}
<div class="flex flex-col items-center justify-center py-[48px] px-[24px] text-center">
<p class="text-[var(--color-text-muted)] text-[14px] mb-[16px]">Noch keine Rezepte vorhanden.</p>
<a href="/recipes/new" class="font-sans text-[13px] font-medium tracking-[0.04em] rounded-[var(--radius-md)] bg-[var(--green-dark)] px-[24px] py-[12px] text-white">
Rezept hinzufügen
</a>
</div>
{/if}

View File

@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import RecipeGrid from './RecipeGrid.svelte';
const mockRecipes = [
{ id: 'r1', name: 'Spaghetti Bolognese', cookTimeMin: 30, effort: 'Easy' },
{ id: 'r2', name: 'Chicken Curry', cookTimeMin: 45, effort: 'Medium' },
{ id: 'r3', name: 'Caesar Salad', cookTimeMin: 15, effort: 'Easy' }
];
describe('RecipeGrid', () => {
it('renders a card for each recipe', () => {
render(RecipeGrid, { props: { recipes: mockRecipes } });
expect(screen.getByText('Spaghetti Bolognese')).toBeInTheDocument();
expect(screen.getByText('Chicken Curry')).toBeInTheDocument();
expect(screen.getByText('Caesar Salad')).toBeInTheDocument();
});
it('renders 3 links for 3 recipes', () => {
render(RecipeGrid, { props: { recipes: mockRecipes } });
expect(screen.getAllByRole('link')).toHaveLength(3);
});
it('shows empty state when recipes array is empty', () => {
render(RecipeGrid, { props: { recipes: [] } });
expect(screen.getByText(/keine rezepte/i)).toBeInTheDocument();
});
it('empty state links to recipe creation', () => {
render(RecipeGrid, { props: { recipes: [] } });
const addLink = screen.getByRole('link', { name: /rezept hinzufügen/i });
expect(addLink).toHaveAttribute('href', '/recipes/new');
});
it('grid has 2-col mobile and 4-col desktop classes', () => {
render(RecipeGrid, { props: { recipes: mockRecipes } });
const grid = document.querySelector('[data-testid="recipe-grid"]');
expect(grid?.className).toContain('grid-cols-2');
expect(grid?.className).toContain('lg:grid-cols-4');
});
});