const { test, expect } = require('@playwright/test'); const BASE = 'https://eetdagboek.vantwout.dev'; /** * Helpers for glucose tests */ /** Navigate to diary tab and wait for render */ async function openDiary(page) { await page.locator('#navDagboek').click(); await page.waitForTimeout(600); // Wait for glucose section to render await expect(page.locator('.glucose-section')).toBeVisible({ timeout: 5000 }); } /** Fill a glucose input and trigger save-on-blur */ async function setGlucoseValue(page, momentKey, value) { const input = page.locator(`input.glucose-input[data-moment="${momentKey}"]`); await input.fill(String(value)); // Trigger blur — saves to localStorage and re-renders the grid await input.evaluate(el => el.blur()); // Wait for re-render to complete await page.waitForTimeout(400); } /** Get the parent .glucose-item for a given moment */ function glucoseItem(page, momentKey) { return page.locator(`input.glucose-input[data-moment="${momentKey}"]`).locator('..').locator('..'); } /** Get status text element for a given moment */ function statusText(page, momentKey) { return page.locator(`input.glucose-input[data-moment="${momentKey}"]`) .locator('..') // .glucose-input-wrapper .locator('..') // .glucose-item .locator('.glucose-status-text'); } // ============================================================================= test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistentie', () => { // ---- 1. Glucose-sectie zichtbaar ---- test('1. glucose section has 4 input fields with correct labels and target ranges', async ({ page }) => { await page.goto(BASE); await openDiary(page); // Section structure await expect(page.locator('.glucose-section')).toBeVisible(); await expect(page.locator('.glucose-title')).toHaveText('Glucose'); await expect(page.locator('.glucose-unit-label')).toHaveText('mmol/L'); await expect(page.locator('.glucose-header .glucose-icon i')).toBeVisible(); // 4 grid items const items = page.locator('.glucose-item'); await expect(items).toHaveCount(4); // Labels await expect(items.nth(0).locator('.glucose-item-label')).toHaveText('Nuchter'); await expect(items.nth(1).locator('.glucose-item-label')).toHaveText('Ontbijt'); await expect(items.nth(2).locator('.glucose-item-label')).toHaveText('Lunch'); await expect(items.nth(3).locator('.glucose-item-label')).toHaveText('Avondeten'); // Target ranges await expect(items.nth(0).locator('.glucose-item-range')).toHaveText('< 5.4'); await expect(items.nth(1).locator('.glucose-item-range')).toHaveText('5.4\u20136.7'); await expect(items.nth(2).locator('.glucose-item-range')).toHaveText('5.4\u20136.7'); await expect(items.nth(3).locator('.glucose-item-range')).toHaveText('5.4\u20136.7'); // Input attributes const moments = ['nuchter', 'naOntbijt', 'naLunch', 'naAvondeten']; for (let i = 0; i < 4; i++) { const input = items.nth(i).locator('.glucose-input'); await expect(input).toHaveAttribute('data-moment', moments[i]); await expect(input).toHaveAttribute('step', '0.1'); await expect(input).toHaveAttribute('min', '0'); await expect(input).toHaveAttribute('max', '30'); await expect(input).toHaveAttribute('inputmode', 'decimal'); await expect(input).toHaveAttribute('placeholder', '\u2014'); } }); // ---- 2. Nuchter binnen range (groen) ---- test('2. nuchter waarde 5.1 (binnen < 5.4) toont groene indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); await setGlucoseValue(page, 'nuchter', 5.1); // Check the item class and status const item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-icon')).toContainText('\u2705'); // ✅ await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); }); // ---- 3. Nuchter ≥ 5.4 (rood) ---- test('3. nuchter waarde 5.4 (te hoog, ≥ 5.4) toont rode indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); await setGlucoseValue(page, 'nuchter', 5.4); const item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-bad/); await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog'); }); // ---- 4. Postprandiaal < 5.4 (rood/te laag) ---- test('4. naOntbijt waarde 5.3 (te laag, < 5.4) toont rode indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); await setGlucoseValue(page, 'naOntbijt', 5.3); const item = glucoseItem(page, 'naOntbijt'); await expect(item).toHaveClass(/glucose-bad/); await expect(item.locator('.glucose-status-text')).toHaveText('Te laag'); }); // ---- 5. Postprandiaal binnen range (groen) ---- test('5. naOntbijt waarde 6.0 (binnen 5.4\u20136.7) toont groene indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); await setGlucoseValue(page, 'naOntbijt', 6.0); const item = glucoseItem(page, 'naOntbijt'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); }); // ---- 6. Postprandiaal > 6.7 (rood/te hoog) ---- test('6. naLunch waarde 6.8 (te hoog, > 6.7) toont rode indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); await setGlucoseValue(page, 'naLunch', 6.8); const item = glucoseItem(page, 'naLunch'); await expect(item).toHaveClass(/glucose-bad/); await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog'); }); // ---- 7. Leeg veld — grijze 'nog meten' indicator ---- test('7. lege velden tonen grijze nog-meten indicator', async ({ page }) => { await page.goto(BASE); await openDiary(page); // All 4 items should start empty const items = page.locator('.glucose-item'); const count = await items.count(); expect(count).toBe(4); for (let i = 0; i < count; i++) { await expect(items.nth(i)).toHaveClass(/glucose-empty/); } const statusTexts = page.locator('.glucose-status-text'); const texts = await statusTexts.allTextContents(); for (const t of texts) { expect(t).toBe('Nog meten'); } // Status icons should be ⏹️ const icons = page.locator('.glucose-status-icon'); const iconTexts = await icons.allTextContents(); for (const icon of iconTexts) { expect(icon.includes('\u23F9\uFE0F') || icon.includes('\u23F9')).toBeTruthy(); } // All inputs should have empty value const inputs = page.locator('.glucose-input'); const inputCount = await inputs.count(); for (let i = 0; i < inputCount; i++) { await expect(inputs.nth(i)).toHaveValue(''); } }); // ---- 8. Persistentie — waarde blijft na page reload ---- test('8. persistentie: waarde blijft na page reload bewaard', async ({ page }) => { await page.goto(BASE); await openDiary(page); // Set a nuchter value await setGlucoseValue(page, 'nuchter', 5.1); // Verify it's shown correctly let item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); await expect(item.locator('.glucose-input')).toHaveValue('5.1'); // Reload the page await page.goto(BASE); await openDiary(page); // Value should still be there item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); await expect(item.locator('.glucose-input')).toHaveValue('5.1'); }); // ---- 9. Datum-navigatie — andere datum andere waarden ---- test('9. datum-navigatie: andere datum toont lege glucose waarden', async ({ page }) => { await page.goto(BASE); await openDiary(page); // Set a nuchter value for today await setGlucoseValue(page, 'nuchter', 5.1); // Verify today shows the value let item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-good/); // Navigate to tomorrow await page.locator('#dayNext').click(); await page.waitForTimeout(600); // Tomorrow should have all empty glucose fields item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-empty/); await expect(item.locator('.glucose-status-text')).toHaveText('Nog meten'); await expect(item.locator('.glucose-input')).toHaveValue(''); // Also check the other moments are empty too for (const moment of ['naOntbijt', 'naLunch', 'naAvondeten']) { const otherItem = glucoseItem(page, moment); await expect(otherItem).toHaveClass(/glucose-empty/); } // Navigate back to today await page.locator('#dayToday').click(); await page.waitForTimeout(600); // Today's value should still be there item = glucoseItem(page, 'nuchter'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); await expect(item.locator('.glucose-input')).toHaveValue('5.1'); }); // ---- 10. Decimale invoer — step 0.1 ---- test('10. decimale invoer: step 0.1 accepteert 5.5 en 6.2 correct', async ({ page }) => { await page.goto(BASE); await openDiary(page); // Check step attribute const input = page.locator('input.glucose-input[data-moment="nuchter"]'); await expect(input).toHaveAttribute('step', '0.1'); // Enter 5.5 (decimal) await setGlucoseValue(page, 'nuchter', 5.5); let item = glucoseItem(page, 'nuchter'); await expect(item.locator('.glucose-input')).toHaveValue('5.5'); // 5.5 ≥ 5.4 for nuchter → should be bad/Te hoog await expect(item).toHaveClass(/glucose-bad/); await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog'); // Now test a postprandial decimal value await setGlucoseValue(page, 'naOntbijt', 6.2); item = glucoseItem(page, 'naOntbijt'); await expect(item.locator('.glucose-input')).toHaveValue('6.2'); await expect(item).toHaveClass(/glucose-good/); await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range'); }); });