C4 sheet content: Empfohlen section with variety delta badges, Alle Rezepte with client-side search filter. GET /planner endpoint proxies suggestions to backend for lazy client-side loading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/svelte';
|
|
import userEvent from '@testing-library/user-event';
|
|
import RecipePicker from './RecipePicker.svelte';
|
|
|
|
const suggestions = [
|
|
{ recipe: { id: 's1', name: 'Lachsfilet', effort: 'easy', cookTimeMin: 25 }, simulatedScore: 9.5 },
|
|
{ recipe: { id: 's2', name: 'Hähnchen-Curry', effort: 'easy', cookTimeMin: 35 }, simulatedScore: 6.0 }
|
|
];
|
|
|
|
const allRecipes = [
|
|
{ id: 'r1', name: 'Beef Bourguignon', effort: 'hard', cookTimeMin: 150 },
|
|
{ id: 'r2', name: 'Spaghetti Carbonara', effort: 'easy', cookTimeMin: 20 },
|
|
{ id: 'r3', name: 'Tomatensuppe', effort: 'easy', cookTimeMin: 30 }
|
|
];
|
|
|
|
const baseProps = {
|
|
planId: 'plan-1',
|
|
date: '2026-04-05',
|
|
dateLabel: 'Samstag, 5. April',
|
|
currentVarietyScore: 7.5,
|
|
suggestions,
|
|
allRecipes,
|
|
onpick: vi.fn()
|
|
};
|
|
|
|
describe('RecipePicker', () => {
|
|
it('shows date label in header', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
expect(screen.getByText('Samstag, 5. April')).toBeTruthy();
|
|
});
|
|
|
|
it('shows Empfohlen section', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
expect(screen.getByText(/Empfohlen/i)).toBeTruthy();
|
|
});
|
|
|
|
it('shows all suggestion recipe names', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
expect(screen.getByText('Lachsfilet')).toBeTruthy();
|
|
expect(screen.getByText('Hähnchen-Curry')).toBeTruthy();
|
|
});
|
|
|
|
it('shows green badge for suggestions with positive delta', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
// Lachsfilet: simulatedScore 9.5 - currentVarietyScore 7.5 = +2 → green badge
|
|
const badge = screen.getByTestId('badge-s1');
|
|
expect(badge.getAttribute('data-type')).toBe('good');
|
|
});
|
|
|
|
it('shows yellow badge for suggestions with zero or negative delta', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
// Hähnchen-Curry: 6.0 - 7.5 = -1.5 → yellow badge
|
|
const badge = screen.getByTestId('badge-s2');
|
|
expect(badge.getAttribute('data-type')).toBe('warning');
|
|
});
|
|
|
|
it('shows Alle Rezepte section', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
expect(screen.getByText(/Alle Rezepte/i)).toBeTruthy();
|
|
});
|
|
|
|
it('shows all recipe names in Alle Rezepte', () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
expect(screen.getByText('Beef Bourguignon')).toBeTruthy();
|
|
expect(screen.getByText('Spaghetti Carbonara')).toBeTruthy();
|
|
expect(screen.getByText('Tomatensuppe')).toBeTruthy();
|
|
});
|
|
|
|
it('filters recipes by search query', async () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
const input = screen.getByRole('searchbox');
|
|
await userEvent.type(input, 'Spaghetti');
|
|
expect(screen.queryByText('Beef Bourguignon')).toBeNull();
|
|
expect(screen.getByText('Spaghetti Carbonara')).toBeTruthy();
|
|
});
|
|
|
|
it('calls onpick with recipeId and name when Wählen clicked for suggestion', async () => {
|
|
const onpick = vi.fn();
|
|
render(RecipePicker, { props: { ...baseProps, onpick } });
|
|
const buttons = screen.getAllByRole('button', { name: /Wählen/i });
|
|
await userEvent.click(buttons[0]);
|
|
expect(onpick).toHaveBeenCalledWith('s1', 'Lachsfilet');
|
|
});
|
|
|
|
it('calls onpick when Wählen clicked for all-recipes item', async () => {
|
|
const onpick = vi.fn();
|
|
render(RecipePicker, { props: { ...baseProps, onpick } });
|
|
const buttons = screen.getAllByRole('button', { name: /Wählen/i });
|
|
// First 2 are suggestions, rest are allRecipes
|
|
await userEvent.click(buttons[2]);
|
|
expect(onpick).toHaveBeenCalledWith('r1', 'Beef Bourguignon');
|
|
});
|
|
|
|
it('shows empty state when search has no results', async () => {
|
|
render(RecipePicker, { props: baseProps });
|
|
const input = screen.getByRole('searchbox');
|
|
await userEvent.type(input, 'xyznotfound');
|
|
expect(screen.getByText(/Keine Treffer/i)).toBeTruthy();
|
|
});
|
|
});
|