2026-04-21 23:50:05 +02:00
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
|
import { setActiveProfile, HENDRIK_ID } from './fixtures/profile';
|
|
|
|
|
import { clearShoppingCart } from './fixtures/api-cleanup';
|
|
|
|
|
|
|
|
|
|
test.describe('Einkaufsliste E2E', () => {
|
|
|
|
|
test.afterEach(async ({ request }) => {
|
|
|
|
|
await clearShoppingCart(request);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Cart-Button auf der Wunschliste erzeugt Header-Badge', async ({ page, request }) => {
|
|
|
|
|
await setActiveProfile(page, HENDRIK_ID);
|
|
|
|
|
// Voraussetzung: Dev-System hat mindestens einen Wunschlisten-Eintrag
|
|
|
|
|
const wlRes = await request.get('/api/wishlist?sort=popular');
|
|
|
|
|
const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] };
|
|
|
|
|
test.skip(wlBody.entries.length === 0, 'Wunschliste leer auf Dev — Test uebersprungen');
|
|
|
|
|
|
|
|
|
|
await page.goto('/wishlist');
|
|
|
|
|
await page.getByLabel('In den Einkaufswagen').first().click();
|
|
|
|
|
await expect(page.getByLabel(/Einkaufsliste \(\d+\)/)).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Shopping-List-Seite zeigt Rezept-Chip + Zutaten', async ({ page, request }) => {
|
|
|
|
|
await setActiveProfile(page, HENDRIK_ID);
|
|
|
|
|
const wlRes = await request.get('/api/wishlist?sort=popular');
|
|
|
|
|
const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] };
|
|
|
|
|
test.skip(wlBody.entries.length === 0, 'Wunschliste leer');
|
|
|
|
|
const recipeId = wlBody.entries[0].recipe_id;
|
|
|
|
|
|
|
|
|
|
await request.post('/api/shopping-list/recipe', { data: { recipe_id: recipeId } });
|
|
|
|
|
await page.goto('/shopping-list');
|
|
|
|
|
|
|
|
|
|
await expect(page.getByRole('heading', { level: 1, name: 'Einkaufsliste' })).toBeVisible();
|
|
|
|
|
// Chip fuers Rezept sichtbar
|
|
|
|
|
await expect(page.getByLabel('Portion weniger').first()).toBeVisible();
|
|
|
|
|
// Mindestens eine Zutatenzeile
|
|
|
|
|
await expect(page.locator('.row').first()).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Portions-Stepper veraendert Mengen live', async ({ page, request }) => {
|
|
|
|
|
await setActiveProfile(page, HENDRIK_ID);
|
|
|
|
|
const wlRes = await request.get('/api/wishlist?sort=popular');
|
|
|
|
|
const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] };
|
|
|
|
|
test.skip(wlBody.entries.length === 0, 'Wunschliste leer');
|
|
|
|
|
|
|
|
|
|
await request.post('/api/shopping-list/recipe', {
|
|
|
|
|
data: { recipe_id: wlBody.entries[0].recipe_id, servings: 4 }
|
|
|
|
|
});
|
|
|
|
|
await page.goto('/shopping-list');
|
|
|
|
|
// Menge der ersten Zeile "vorher" lesen
|
|
|
|
|
const qtyBefore = await page.locator('.qty').first().textContent();
|
|
|
|
|
// Portion +1
|
|
|
|
|
await page.getByLabel('Portion mehr').first().click();
|
|
|
|
|
// Nach Fetch+Rerender muss die Menge sich aendern (ungleich dem Vorher-Wert)
|
|
|
|
|
await expect
|
|
|
|
|
.poll(async () => (await page.locator('.qty').first().textContent())?.trim())
|
|
|
|
|
.not.toBe(qtyBefore?.trim());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Abhaken: Zeile durchgestrichen, Badge-Count sinkt, persistiert nach Reload', async ({ page, request }) => {
|
|
|
|
|
await setActiveProfile(page, HENDRIK_ID);
|
|
|
|
|
const wlRes = await request.get('/api/wishlist?sort=popular');
|
|
|
|
|
const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] };
|
|
|
|
|
test.skip(wlBody.entries.length === 0, 'Wunschliste leer');
|
|
|
|
|
|
|
|
|
|
await request.post('/api/shopping-list/recipe', {
|
|
|
|
|
data: { recipe_id: wlBody.entries[0].recipe_id }
|
|
|
|
|
});
|
|
|
|
|
await page.goto('/shopping-list');
|
|
|
|
|
|
|
|
|
|
const countBadge = page.getByLabel(/Einkaufsliste \(\d+\)/);
|
|
|
|
|
const badgeTextBefore = await countBadge.textContent();
|
|
|
|
|
const numBefore = Number((badgeTextBefore ?? '').replace(/\D+/g, '')) || 0;
|
|
|
|
|
|
|
|
|
|
const firstRow = page.locator('label.row').first();
|
|
|
|
|
await firstRow.locator('input[type=checkbox]').check();
|
|
|
|
|
await expect(firstRow).toHaveClass(/checked/);
|
|
|
|
|
|
|
|
|
|
// Badge muss sinken (nach Store-Refresh)
|
|
|
|
|
await expect
|
|
|
|
|
.poll(async () => {
|
|
|
|
|
const t = (await countBadge.textContent()) ?? '';
|
|
|
|
|
return Number(t.replace(/\D+/g, '')) || 0;
|
|
|
|
|
})
|
|
|
|
|
.toBeLessThan(numBefore);
|
|
|
|
|
|
|
|
|
|
// Reload persistiert
|
|
|
|
|
await page.reload();
|
|
|
|
|
await expect(page.locator('label.row.checked').first()).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Liste leeren: Confirm + Empty-State + Badge weg', async ({ page, request }) => {
|
|
|
|
|
await setActiveProfile(page, HENDRIK_ID);
|
|
|
|
|
const wlRes = await request.get('/api/wishlist?sort=popular');
|
|
|
|
|
const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] };
|
|
|
|
|
test.skip(wlBody.entries.length === 0, 'Wunschliste leer');
|
|
|
|
|
|
|
|
|
|
await request.post('/api/shopping-list/recipe', {
|
|
|
|
|
data: { recipe_id: wlBody.entries[0].recipe_id }
|
|
|
|
|
});
|
|
|
|
|
await page.goto('/shopping-list');
|
|
|
|
|
|
|
|
|
|
await page.getByRole('button', { name: 'Liste leeren' }).click();
|
|
|
|
|
// Confirm-Dialog (ConfirmAction nutzt einen App-eigenen Dialog, kein native)
|
2026-04-21 23:52:49 +02:00
|
|
|
await page.getByRole('button', { name: 'Leeren', exact: true }).click();
|
2026-04-21 23:50:05 +02:00
|
|
|
|
|
|
|
|
await expect(page.getByText('Einkaufswagen ist leer.')).toBeVisible();
|
|
|
|
|
await expect(page.getByLabel(/Einkaufsliste \(\d+\)/)).toHaveCount(0);
|
|
|
|
|
});
|
|
|
|
|
});
|