feat: glucose-metingen — nuchter + postprandiaal met streefwaardes
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)
This commit is contained in:
parent
809f289a46
commit
dbe39fded7
3 changed files with 598 additions and 2 deletions
314
index.html
314
index.html
|
|
@ -465,6 +465,161 @@ body {
|
|||
background: var(--progress-red);
|
||||
}
|
||||
|
||||
/* ===== Glucose Section ===== */
|
||||
.glucose-section {
|
||||
background: var(--white);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.glucose-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.glucose-icon {
|
||||
font-size: 1.1rem;
|
||||
color: var(--progress-red);
|
||||
}
|
||||
|
||||
.glucose-title {
|
||||
font-weight: 700;
|
||||
font-size: var(--font-md);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.glucose-unit-label {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-muted);
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* Grid: 4 columns on wide, 2×2 on narrow */
|
||||
.glucose-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.glucose-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.glucose-item {
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 8px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--border);
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.glucose-item.glucose-good {
|
||||
border-color: #28a745;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.glucose-item.glucose-bad {
|
||||
border-color: var(--progress-red);
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.glucose-item.glucose-empty {
|
||||
border-color: var(--border);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.glucose-item-label {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.glucose-item-range {
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.glucose-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.glucose-input {
|
||||
width: 58px;
|
||||
padding: 4px 4px;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--radius-xs);
|
||||
font-size: var(--font-md);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
background: var(--white);
|
||||
color: var(--text);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
/* Hide spinners */
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.glucose-input::-webkit-outer-spin-button,
|
||||
.glucose-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.glucose-input:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(250,232,146,0.25);
|
||||
}
|
||||
|
||||
.glucose-unit {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.glucose-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
font-size: var(--font-xs);
|
||||
line-height: 1.2;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.glucose-status-icon {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.glucose-status-text {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.glucose-good .glucose-status-text {
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.glucose-bad .glucose-status-text {
|
||||
color: var(--progress-red);
|
||||
}
|
||||
|
||||
.glucose-empty .glucose-status-text {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Warning text */
|
||||
.carb-warning {
|
||||
display: none;
|
||||
|
|
@ -2103,6 +2258,16 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
<div class="carb-warning" id="carbWarning"></div>
|
||||
</div>
|
||||
|
||||
<!-- Glucose Section -->
|
||||
<div class="glucose-section" id="glucoseSection">
|
||||
<div class="glucose-header">
|
||||
<span class="glucose-icon"><i class="fa-solid fa-droplet"></i></span>
|
||||
<span class="glucose-title">Glucose</span>
|
||||
<span class="glucose-unit-label">mmol/L</span>
|
||||
</div>
|
||||
<div class="glucose-grid" id="glucoseGrid"></div>
|
||||
</div>
|
||||
|
||||
<ul class="eetmoment-list" id="eetmomentList"></ul>
|
||||
|
||||
<div class="empty-state hidden" id="diaryEmpty">
|
||||
|
|
@ -2288,6 +2453,14 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
return CATEGORY_MAP[itemCat] || 'Overig';
|
||||
}
|
||||
|
||||
// ===== Prikmomenten voor glucose =====
|
||||
const PRICKMOMENTEN = [
|
||||
{ key: 'nuchter', label: 'Nuchter (voor ontbijt)', targetMin: 0, targetMax: 5.4 },
|
||||
{ key: 'naOntbijt', label: '2 uur na ontbijt', targetMin: 5.4, targetMax: 6.7 },
|
||||
{ key: 'naLunch', label: '2 uur na lunch', targetMin: 5.4, targetMax: 6.7 },
|
||||
{ key: 'naAvondeten', label: '2 uur na avondeten', targetMin: 5.4, targetMax: 6.7 }
|
||||
];
|
||||
|
||||
// ===== Standaard portiegroottes per categorie =====
|
||||
// Gebaseerd op Voedingscentrum / NEVO richtlijnen
|
||||
/** Drie praktische porties per categorie: een intuïtieve eenheid + 100g + 50g */
|
||||
|
|
@ -2369,6 +2542,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
let searchIndex = null; // Map<item, expanded search tokens string>
|
||||
let dataLoaded = false;
|
||||
let maaltijden = []; // [{ id, naam, items: [{item, portie}, ...], khTotaal }]
|
||||
let glucose = {}; // { '2026-07-24': { nuchter: 5.1, naOntbijt: 6.2, naLunch: null, naAvondeten: null } }
|
||||
const SEARCH_PAGE_SIZE = 50;
|
||||
let searchVisibleCount = SEARCH_PAGE_SIZE;
|
||||
let searchObserver = null;
|
||||
|
|
@ -2461,6 +2635,8 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
getOrCreateDay(activeDate);
|
||||
// Load maaltijden
|
||||
loadMaaltijdenFromStorage();
|
||||
// Load glucose data
|
||||
loadGlucoseFromStorage();
|
||||
}
|
||||
|
||||
function saveMaaltijden() {
|
||||
|
|
@ -2482,6 +2658,139 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
}
|
||||
|
||||
// ===== Glucose =====
|
||||
function isGlucoseInRange(value, moment) {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
const m = PRICKMOMENTEN.find(p => p.key === moment);
|
||||
if (!m) return null;
|
||||
// Nuchter (fasting): only upper bound — < targetMax is good
|
||||
if (moment === 'nuchter') {
|
||||
return value < m.targetMax ? 'good' : 'high';
|
||||
}
|
||||
// Post-meal: range 5.4 – 6.7 (inclusive)
|
||||
if (value < m.targetMin) return 'low';
|
||||
if (value > m.targetMax) return 'high';
|
||||
return 'good';
|
||||
}
|
||||
|
||||
function saveGlucoseToStorage() {
|
||||
try {
|
||||
localStorage.setItem('koolhydraat-glucose', JSON.stringify(glucose));
|
||||
} catch (e) {
|
||||
// Storage full — silently ignore
|
||||
}
|
||||
}
|
||||
|
||||
function loadGlucoseFromStorage() {
|
||||
try {
|
||||
const raw = localStorage.getItem('koolhydraat-glucose');
|
||||
if (raw) {
|
||||
glucose = JSON.parse(raw);
|
||||
}
|
||||
} catch (e) {
|
||||
glucose = {};
|
||||
}
|
||||
// Ensure today's entry exists
|
||||
if (!glucose[activeDate]) {
|
||||
glucose[activeDate] = {};
|
||||
PRICKMOMENTEN.forEach(m => { glucose[activeDate][m.key] = null; });
|
||||
}
|
||||
}
|
||||
|
||||
function ensureGlucoseDay(dateStr) {
|
||||
if (!glucose[dateStr]) {
|
||||
glucose[dateStr] = {};
|
||||
PRICKMOMENTEN.forEach(m => { glucose[dateStr][m.key] = null; });
|
||||
}
|
||||
}
|
||||
|
||||
function renderGlucoseSection() {
|
||||
const grid = document.getElementById('glucoseGrid');
|
||||
if (!grid) return;
|
||||
|
||||
ensureGlucoseDay(activeDate);
|
||||
|
||||
grid.innerHTML = PRICKMOMENTEN.map(m => {
|
||||
const value = glucose[activeDate][m.key];
|
||||
const status = isGlucoseInRange(value, m.key);
|
||||
|
||||
let statusIcon, statusClass, statusText;
|
||||
if (value === null || value === undefined || value === '') {
|
||||
statusIcon = '⏹️';
|
||||
statusClass = 'glucose-empty';
|
||||
statusText = 'Nog meten';
|
||||
} else if (status === 'good') {
|
||||
statusIcon = '✅';
|
||||
statusClass = 'glucose-good';
|
||||
statusText = 'Binnen range';
|
||||
} else if (status === 'high') {
|
||||
statusIcon = '🔴';
|
||||
statusClass = 'glucose-bad';
|
||||
statusText = 'Te hoog';
|
||||
} else {
|
||||
statusIcon = '🔴';
|
||||
statusClass = 'glucose-bad';
|
||||
statusText = 'Te laag';
|
||||
}
|
||||
|
||||
const displayValue = (value !== null && value !== undefined) ? value : '';
|
||||
|
||||
// Short label: remove parenthetical and "2 uur na" prefix
|
||||
let shortLabel = m.label;
|
||||
shortLabel = shortLabel.replace(/\([^)]*\)/g, '').trim();
|
||||
shortLabel = shortLabel.replace(/^2 uur na /i, '').trim();
|
||||
// Capitalize first letter
|
||||
shortLabel = shortLabel.charAt(0).toUpperCase() + shortLabel.slice(1);
|
||||
|
||||
return `
|
||||
<div class="glucose-item ${statusClass}">
|
||||
<div class="glucose-item-label">${shortLabel}</div>
|
||||
<div class="glucose-item-range">${m.key === 'nuchter' ? '< ' + m.targetMax : m.targetMin + '–' + m.targetMax}</div>
|
||||
<div class="glucose-input-wrapper">
|
||||
<input type="number" class="glucose-input"
|
||||
value="${displayValue}"
|
||||
data-moment="${m.key}"
|
||||
step="0.1" min="0" max="30"
|
||||
placeholder="—"
|
||||
inputmode="decimal">
|
||||
<span class="glucose-unit">mmol/L</span>
|
||||
</div>
|
||||
<div class="glucose-status ${statusClass}">
|
||||
<span class="glucose-status-icon">${statusIcon}</span>
|
||||
<span class="glucose-status-text">${statusText}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Attach save-on-blur handlers
|
||||
grid.querySelectorAll('.glucose-input').forEach(input => {
|
||||
input.addEventListener('blur', () => {
|
||||
const moment = input.dataset.moment;
|
||||
const val = input.value.trim() === '' ? null : parseFloat(input.value);
|
||||
|
||||
// Validate
|
||||
if (val !== null && (isNaN(val) || val < 0 || val > 30)) {
|
||||
input.value = glucose[activeDate][moment] !== null && glucose[activeDate][moment] !== undefined ? glucose[activeDate][moment] : '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (glucose[activeDate][moment] !== val) {
|
||||
glucose[activeDate][moment] = val;
|
||||
saveGlucoseToStorage();
|
||||
renderGlucoseSection();
|
||||
}
|
||||
});
|
||||
|
||||
// Enter key also saves
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
input.blur();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Daily Carb Limit =====
|
||||
const DEFAULT_CARB_LIMIT = 175; // grams per day
|
||||
|
||||
|
|
@ -2875,6 +3184,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
// Progress bar
|
||||
renderProgressBar(totaal);
|
||||
|
||||
// Glucose section
|
||||
renderGlucoseSection();
|
||||
|
||||
// Eating moments
|
||||
const listEl = document.getElementById('eetmomentList');
|
||||
listEl.innerHTML = EETMOMENTEN.map((moment, mi) => {
|
||||
|
|
@ -3664,12 +3976,14 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
current.setDate(current.getDate() + direction);
|
||||
activeDate = formatDate(current);
|
||||
getOrCreateDay(activeDate);
|
||||
ensureGlucoseDay(activeDate);
|
||||
renderDagboek();
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
activeDate = formatDate(new Date());
|
||||
getOrCreateDay(activeDate);
|
||||
ensureGlucoseDay(activeDate);
|
||||
renderDagboek();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"ed088d4b1ffb8f06e682-69d589b2ce0725f7c6b3"
|
||||
]
|
||||
}
|
||||
280
tests/glucose-e2e.spec.js
Normal file
280
tests/glucose-e2e.spec.js
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
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');
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue