Files
mealprep/frontend/src/routes/(app)/planner/page.test.ts

147 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, waitFor, within } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import Page from './+page.svelte';
vi.mock('$app/navigation', () => ({ goto: vi.fn(), invalidateAll: vi.fn() }));
vi.mock('$app/forms', () => ({
enhance: () => () => ({ destroy: () => {} })
}));
const PLAN_ID = 'plan-00000000-0000-0000-0000-000000000001';
// Use a past week so "today" is never in this range — selectedDay defaults to weekStart (Monday)
const DATE = '2025-01-06'; // Monday, January 6 2025
const mockData = {
weekPlan: { id: PLAN_ID, weekStart: DATE, status: 'draft', slots: [] as any[] },
varietyScore: null,
weekStart: DATE,
recipes: [{ id: 'r1', name: 'Beef Bourguignon', effort: 'hard', cookTimeMin: 150 }],
benutzer: { rolle: 'planer' }
};
const mockDataWithSlot = {
...mockData,
weekPlan: {
id: PLAN_ID,
weekStart: DATE,
status: 'draft',
slots: [{ id: 'slot-1', slotDate: DATE, recipe: { id: 'r1', name: 'Beef Bourguignon', effort: 'hard', cookTimeMin: 150 } }]
}
};
const mockSuggestions = [
{
recipe: { id: 's1', name: 'Lachsfilet', effort: 'easy', cookTimeMin: 20 },
scoreDelta: 1.5,
hasConflict: false
}
];
describe('+page.svelte — $effect suggestion fetch', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('calls fetch when picker opens with correct planId and date', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValueOnce({
json: () => Promise.resolve({ suggestions: mockSuggestions })
})
);
render(Page, { props: { data: mockData } });
await userEvent.click(screen.getAllByRole('button', { name: /Gericht/i })[0]);
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
expect((fetch as any).mock.calls[0][0]).toContain(`planId=${PLAN_ID}`);
expect((fetch as any).mock.calls[0][0]).toContain(`date=${DATE}`);
});
it('shows suggestions in RecipePicker after fetch resolves', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValueOnce({
json: () => Promise.resolve({ suggestions: mockSuggestions })
})
);
render(Page, { props: { data: mockData } });
await userEvent.click(screen.getAllByRole('button', { name: /Gericht/i })[0]);
expect(await screen.findByText('Lachsfilet')).toBeTruthy();
});
it('passes AbortSignal to fetch so inflight requests can be cancelled', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValueOnce({
json: () => Promise.resolve({ suggestions: [] })
})
);
render(Page, { props: { data: mockData } });
await userEvent.click(screen.getAllByRole('button', { name: /Gericht/i })[0]);
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
const fetchOptions = (fetch as any).mock.calls[0][1];
expect(fetchOptions?.signal).toBeInstanceOf(AbortSignal);
});
});
describe('+page.svelte — swap sheet suggestion fetch', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('opening mobile swap sheet triggers fetch with planId and date', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ json: () => Promise.resolve({ suggestions: [] }) }));
render(Page, { props: { data: mockDataWithSlot } });
// Open action sheet, then swap sheet
await userEvent.click(screen.getByTestId('day-meal-card'));
await userEvent.click(await screen.findByRole('button', { name: /Gericht tauschen/i }));
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
expect((fetch as any).mock.calls[0][0]).toContain(`planId=${PLAN_ID}`);
expect((fetch as any).mock.calls[0][0]).toContain(`date=${DATE}`);
});
});
describe('+page.svelte — remove meal', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('clicking Entfernen in MealActionSheet shows undo bar with recipe name', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ json: () => Promise.resolve({ suggestions: [] }) }));
render(Page, { props: { data: mockDataWithSlot } });
await userEvent.click(screen.getByTestId('day-meal-card'));
await userEvent.click(await screen.findByRole('button', { name: /Entfernen/i }));
const undoBar = screen.getByTestId('undo-bar');
expect(undoBar).toBeTruthy();
expect(within(undoBar).getByText(/Beef Bourguignon/)).toBeTruthy();
});
it('clicking Rückgängig after remove hides the undo bar', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ json: () => Promise.resolve({ suggestions: [] }) }));
render(Page, { props: { data: mockDataWithSlot } });
await userEvent.click(screen.getByTestId('day-meal-card'));
await userEvent.click(await screen.findByRole('button', { name: /Entfernen/i }));
await userEvent.click(screen.getByRole('button', { name: /Rückgängig/i }));
expect(screen.queryByTestId('undo-bar')).toBeNull();
});
});