karby/tests/e2e.spec.js

355 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { test, expect } = require('@playwright/test');
const BASE = 'https://eetdagboek.vantwout.dev';
test.describe('Karby Eetdagboek', () => {
test('page loads with correct title and elements', async ({ page }) => {
await page.goto(BASE);
await expect(page).toHaveTitle('Karby');
await expect(page.locator('.search-input')).toBeVisible();
await expect(page.locator('.cat-btn').first()).toBeVisible();
await expect(page.locator('#navZoeken')).toBeVisible();
await expect(page.locator('#navDagboek')).toBeVisible();
await expect(page.locator('#navMaaltijden')).toBeVisible();
});
test('search returns results and opens detail modal', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('aardappel');
await page.waitForTimeout(800);
const results = page.locator('.food-item');
await expect(results.first()).toBeVisible({ timeout: 5000 });
await results.first().click();
await expect(page.locator('#detailModal')).not.toHaveClass(/hidden/, { timeout: 5000 });
await page.locator('#detailModal .modal-close').click();
await expect(page.locator('#detailModal')).toHaveClass(/hidden/, { timeout: 3000 });
});
test('category filters work', async ({ page }) => {
await page.goto(BASE);
await page.locator('.cat-btn').filter({ hasText: 'Brood' }).click();
await page.waitForTimeout(500);
const activeBtn = page.locator('.cat-btn.active');
await expect(activeBtn).toBeVisible();
});
test('add item to diary via eetmoment flow', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await expect(page.locator('#detailAddBtn')).toBeVisible({ timeout: 3000 });
});
test('diary tab shows date navigation', async ({ page }) => {
await page.goto(BASE);
await page.locator('#navDagboek').click();
await page.waitForTimeout(500);
await expect(page.locator('.day-navigator')).toBeVisible({ timeout: 3000 });
await expect(page.locator('.day-nav-btn').first()).toBeVisible();
await expect(page.locator('.day-nav-today')).toBeVisible();
});
test('settings modal opens', async ({ page }) => {
await page.goto(BASE);
await page.locator('#settingsBtn').click();
await page.waitForTimeout(500);
await expect(page.locator('#settingsModal')).not.toHaveClass(/hidden/, { timeout: 3000 });
await page.locator('#settingsModal .modal-close').click();
await expect(page.locator('#settingsModal')).toHaveClass(/hidden/, { timeout: 2000 });
});
test('maaltijden tab shows saved meals section', async ({ page }) => {
await page.goto(BASE);
await page.locator('#navMaaltijden').click();
await page.waitForTimeout(500);
await expect(page.locator('#tabMaaltijden')).toBeVisible();
});
test('contrast: key elements have visible colors', async ({ page }) => {
await page.goto(BASE);
const navBtns = page.locator('.nav-btn');
const count = await navBtns.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
const color = await navBtns.nth(i).evaluate(el => getComputedStyle(el).color);
expect(color).not.toBe('rgba(0, 0, 0, 0)');
}
});
// ========== NIEUWE TESTS — portie-steppers, animaties, maaltijden, datumkiezer, meal-groups ==========
// ---------- Stepper: basis ----------
test('portion stepper: steppers appear in addMealModal with first count=1', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
const steppers = page.locator('.portie-stepper');
await expect(steppers.first()).toBeVisible({ timeout: 3000 });
expect(await steppers.count()).toBeGreaterThan(0);
await expect(steppers.first().locator('.portie-stepper-count')).toHaveText('1');
});
test('portion stepper: plus op tweede activeert die en reset eerste naar 0', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
const steppers = page.locator('.portie-stepper');
const count = await steppers.count();
if (count < 2) { test.skip(); return; }
await steppers.nth(1).locator('.portie-stepper-plus').click();
await page.waitForTimeout(100);
await expect(steppers.first().locator('.portie-stepper-count')).toHaveText('0');
await expect(steppers.nth(1).locator('.portie-stepper-count')).toHaveText('1');
});
test('portion stepper: minus verlaagt count naar 0 en wordt dan disabled', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
const firstStepper = page.locator('.portie-stepper').first();
await expect(firstStepper.locator('.portie-stepper-count')).toHaveText('1');
await firstStepper.locator('.portie-stepper-minus').click();
await expect(firstStepper.locator('.portie-stepper-count')).toHaveText('0');
await expect(firstStepper.locator('.portie-stepper-minus')).toBeDisabled();
});
test('portion stepper: active class na plus/minus toggles', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
const firstStepper = page.locator('.portie-stepper').first();
// active class na updatePortionFromSteppers triggeren via plus
await firstStepper.locator('.portie-stepper-plus').click();
await page.waitForTimeout(100);
await expect(firstStepper).toHaveClass(/active/);
// Minus 2×: 2→1→0, dan active kwijt
await firstStepper.locator('.portie-stepper-minus').click();
await page.waitForTimeout(50);
await firstStepper.locator('.portie-stepper-minus').click();
await page.waitForTimeout(50);
await expect(firstStepper).not.toHaveClass(/active/);
});
// ---------- Modal animaties ----------
test('modal close animation: closing class applied during dismiss', async ({ page }) => {
await page.goto(BASE);
await page.locator('#settingsBtn').click();
await page.waitForTimeout(500);
await expect(page.locator('#settingsModal')).not.toHaveClass(/hidden/);
await page.locator('#settingsModal .modal-close').click();
await page.waitForTimeout(50);
await expect(page.locator('#settingsModal')).toHaveClass(/closing/);
await page.waitForTimeout(600);
await expect(page.locator('#settingsModal')).toHaveClass(/hidden/);
});
test('modal open animation: fadeIn en slideUp CSS animations gestart', async ({ page }) => {
await page.goto(BASE);
await page.locator('#settingsBtn').click();
await page.waitForTimeout(100);
const overlay = page.locator('#settingsModal');
await expect(overlay).toBeVisible();
await expect(overlay).not.toHaveClass(/closing/);
const overlayAnim = await overlay.evaluate(el => getComputedStyle(el).animationName);
expect(overlayAnim).toBe('fadeIn');
const sheet = overlay.locator('.modal-sheet');
const sheetAnim = await sheet.evaluate(el => getComputedStyle(el).animationName);
expect(sheetAnim).toBe('slideUp');
await page.locator('#settingsModal .modal-close').click();
await page.waitForTimeout(500);
});
test('tab switching: switching class verschijnt tijdens crossfade', async ({ page }) => {
await page.goto(BASE);
await page.locator('#navDagboek').click();
await page.waitForTimeout(100);
const tabEl = page.locator('#tabDagboek');
await expect(tabEl).toHaveClass(/switching/);
await page.waitForTimeout(500);
await expect(tabEl).toBeVisible();
});
// ---------- Maaltijden ----------
test('meal save: name input is empty by default', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
// Item toevoegen via addMealSubmit → sluit modal + gaat naar dagboek
await page.locator('#addMealSubmit').click();
await page.waitForTimeout(800);
const saveBtn = page.locator('.save-meal-btn');
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
await saveBtn.first().click();
await page.waitForTimeout(500);
const nameInput = page.locator('#mealNameInput');
await expect(nameInput).toBeVisible({ timeout: 5000 });
await expect(nameInput).toHaveValue('');
await page.locator('#mealNameCancel').click();
await page.waitForTimeout(300);
});
test('meal save: empty name toont rode rand en modal blijft open', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
await page.locator('#addMealSubmit').click();
await page.waitForTimeout(800);
const saveBtn = page.locator('.save-meal-btn');
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
await saveBtn.first().click();
await page.waitForTimeout(500);
const nameInput = page.locator('#mealNameInput');
await expect(nameInput).toBeVisible({ timeout: 5000 });
await expect(nameInput).toHaveValue('');
// Opslaan zonder naam → validatiefout
await page.locator('#mealNameSave').click();
await page.waitForTimeout(300);
await expect(nameInput).toBeVisible();
const borderColor = await nameInput.evaluate(el => getComputedStyle(el).borderColor);
const rgb = borderColor.match(/\d+/g).map(Number);
expect(rgb[0]).toBeGreaterThan(150);
expect(rgb[1]).toBeLessThan(100);
expect(rgb[2]).toBeLessThan(100);
await page.locator('#mealNameCancel').click();
await page.waitForTimeout(300);
});
// ---------- Datumkiezer ----------
test('date navigator: next/prev verandert datum en today reset', async ({ page }) => {
await page.goto(BASE);
await page.locator('#navDagboek').click();
await page.waitForTimeout(500);
const initialDay = await page.locator('#dayLabel').textContent();
const nextBtn = page.locator('.day-nav-btn').nth(1);
await nextBtn.click();
await page.waitForTimeout(300);
const nextDay = await page.locator('#dayLabel').textContent();
expect(nextDay).not.toBe(initialDay);
await page.locator('.day-nav-today').click();
await page.waitForTimeout(300);
const todayDay = await page.locator('#dayLabel').textContent();
expect(todayDay).toBe(initialDay);
});
// ---------- Zoek animaties ----------
test('zoektab: food items krijgen staggered animation indices', async ({ page }) => {
await page.goto(BASE);
await page.locator('.search-input').fill('k');
await page.waitForTimeout(800);
const items = page.locator('.food-item');
const count = await items.count();
if (count >= 2) {
const i0 = await items.first().evaluate(el => el.style.getPropertyValue('--i'));
expect(i0.trim()).toBe('0');
const i1 = await items.nth(1).evaluate(el => el.style.getPropertyValue('--i'));
expect(i1.trim()).toBe('1');
}
});
test('button press: buttons hebben transition transform bij indrukken', async ({ page }) => {
await page.goto(BASE);
const btn = page.locator('button').first();
const transition = await btn.evaluate(el => getComputedStyle(el).transition);
expect(transition).toBeTruthy();
expect(transition).toContain('transform');
});
// ---------- Meal-group uitklappen in dagboek ----------
test('meal-group uitklappen: saved meal toggles expand/collapse in dagboek', async ({ page }) => {
await page.goto(BASE);
// Stap 1: Zoek voedingsmiddel en voeg toe aan dagboek
await page.locator('.search-input').fill('brood');
await page.waitForTimeout(800);
await page.locator('.food-item').first().click();
await page.waitForTimeout(500);
await page.locator('#detailAddBtn').click();
await page.waitForTimeout(500);
await page.locator('#addMealSubmit').click();
await page.waitForTimeout(800);
// Stap 2: Sla op als maaltijd
const saveBtn = page.locator('.save-meal-btn');
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
await saveBtn.first().click();
await page.waitForTimeout(500);
const nameInput = page.locator('#mealNameInput');
await expect(nameInput).toBeVisible({ timeout: 5000 });
await nameInput.fill('Test ontbijt');
await page.locator('#mealNameSave').click();
await page.waitForTimeout(600);
// Stap 3: Ga naar maaltijden tab om de maaltijd toe te voegen aan dagboek
await page.locator('#navMaaltijden').click();
await page.waitForTimeout(600);
// Stap 4: Klik "Toevoegen aan dagboek" op de opgeslagen maaltijd
const mealCardAdd = page.locator('.meal-card-add').first();
await expect(mealCardAdd).toBeVisible({ timeout: 5000 });
await mealCardAdd.click();
await page.waitForTimeout(500);
// Stap 5: Klik op een eetmoment-optie in de modal (bv. Ontbijt)
const mealOption = page.locator('.meal-add-option').first();
await expect(mealOption).toBeVisible({ timeout: 3000 });
await mealOption.click();
await page.waitForTimeout(600);
// Sluit eventuele resteerde modals door op de overlay te klikken
const anyOverlay = page.locator('.modal-overlay:not(.hidden)').first();
if (await anyOverlay.isVisible().catch(() => false)) {
await anyOverlay.click({ position: { x: 10, y: 10 } });
await page.waitForTimeout(300);
}
// Stap 6: Ga naar dagboek om de meal-group te zien
await page.locator('#navDagboek').click();
await page.waitForTimeout(800);
// Stap 7: Zoek meal-group
const mealGroup = page.locator('.eetmoment-meal-group').first();
await expect(mealGroup).toBeVisible({ timeout: 5000 });
// Stap 8: Items-wrap begint collapsed
const itemsWrap = mealGroup.locator('.eetmoment-meal-items-wrap');
await expect(itemsWrap).toHaveClass(/hidden/);
// Stap 9: Klik header om uit te klappen
const header = mealGroup.locator('.eetmoment-meal-header');
await header.click();
await page.waitForTimeout(300);
await expect(itemsWrap).not.toHaveClass(/hidden/);
const chevronUp = header.locator('i.fa-chevron-up');
await expect(chevronUp).toBeVisible();
// Stap 10: Klik opnieuw om in te klappen
await header.click();
await page.waitForTimeout(300);
await expect(itemsWrap).toHaveClass(/hidden/);
const chevronDown = header.locator('i.fa-chevron-down');
await expect(chevronDown).toBeVisible();
});
});