import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen } from '@testing-library/svelte'; import { userEvent } from '@testing-library/user-event'; import StaplesManager from './StaplesManager.svelte'; const mockCategories = [ { id: 'cat-1', name: 'Öle & Fette', ingredients: [ { id: 'ing-1', name: 'Olivenöl', isStaple: true }, { id: 'ing-2', name: 'Butter', isStaple: false } ] }, { id: 'cat-2', name: 'Gewürze', ingredients: [ { id: 'ing-3', name: 'Salz', isStaple: true }, { id: 'ing-4', name: 'Pfeffer', isStaple: true } ] } ]; describe('StaplesManager', () => { beforeEach(() => { vi.useFakeTimers(); vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true })); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it('renders all categories', () => { render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); expect(screen.getByText('Öle & Fette')).toBeInTheDocument(); expect(screen.getByText('Gewürze')).toBeInTheDocument(); }); it('renders all chips with correct initial aria-pressed state', () => { render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); expect(screen.getByRole('button', { name: 'Olivenöl' })).toHaveAttribute('aria-pressed', 'true'); expect(screen.getByRole('button', { name: 'Butter' })).toHaveAttribute('aria-pressed', 'false'); expect(screen.getByRole('button', { name: 'Salz' })).toHaveAttribute('aria-pressed', 'true'); }); it('clicking a chip immediately updates aria-pressed (optimistic)', async () => { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); const butter = screen.getByRole('button', { name: 'Butter' }); expect(butter).toHaveAttribute('aria-pressed', 'false'); await user.click(butter); expect(butter).toHaveAttribute('aria-pressed', 'true'); }); it('rapid clicks on same chip result in exactly one fetch call after debounce', async () => { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); const butter = screen.getByRole('button', { name: 'Butter' }); await user.click(butter); await user.click(butter); await user.click(butter); expect(fetch).not.toHaveBeenCalled(); vi.advanceTimersByTime(300); await vi.runAllTimersAsync(); expect(fetch).toHaveBeenCalledTimes(1); }); it('reverts chip and shows error when PATCH fails', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false })); const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); const butter = screen.getByRole('button', { name: 'Butter' }); await user.click(butter); expect(butter).toHaveAttribute('aria-pressed', 'true'); vi.advanceTimersByTime(300); await vi.runAllTimersAsync(); expect(butter).toHaveAttribute('aria-pressed', 'false'); expect(screen.getByText(/konnte nicht gespeichert werden/i)).toBeInTheDocument(); }); it('uses 2-column grid class in onboarding context', () => { render(StaplesManager, { props: { categories: mockCategories, context: 'onboarding' } }); const grid = screen.getByTestId('category-grid'); expect(grid.className).toContain('md:grid-cols-2'); }); it('uses 3-column grid class in settings context', () => { render(StaplesManager, { props: { categories: mockCategories, context: 'settings' } }); const grid = screen.getByTestId('category-grid'); expect(grid.className).toContain('md:grid-cols-3'); }); it('renders without crashing when categories is empty', () => { render(StaplesManager, { props: { categories: [], context: 'onboarding' } }); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); });