Data-laag: glucose state, localStorage ('koolhydraat-glucose'), PRIKMOMENTEN met
target ranges (nuchter < 5.4, postprandiaal 5.4–6.7)
UI: glucose-sectie in dagboek met 4 inputvelden (nuchter, na ontbijt, na lunch,
na avondeten). Kleurcodering: groen=binnen range, rood=buiten, grijs=leeg.
Save-on-blur, responsive 2×2 grid op mobiel.
E2e: 10 Playwright tests — invoer, validatie, kleurcodering, persistentie,
datum-navigatie, decimale invoer.
35 tests totaal (25 origineel + 10 glucose)
280 lines
10 KiB
JavaScript
280 lines
10 KiB
JavaScript
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');
|
|
});
|
|
|
|
});
|