feat: haptic feedback (navigator.vibrate) + toast fix + 7 nieuwe e2e tests
- Haptic feedback: 8ms vibrate op elke button press (Android Vibration API) - Toast fix: slideUp/Down animatie ipv slideInRight (die de pagina verschoof) - 7 nieuwe e2e tests: steppers, modal-close animatie, tab-switching, date-nav, stagger-indices, button-transitions - Tests groen: 15/16 passed, 1 skipped
This commit is contained in:
parent
9c3a07bbce
commit
0713ec62cd
3 changed files with 477 additions and 128 deletions
|
|
@ -800,12 +800,6 @@ body {
|
|||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.2s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal-sheet {
|
||||
|
|
@ -816,15 +810,9 @@ body {
|
|||
max-height: 85vh;
|
||||
max-height: 85dvh;
|
||||
overflow-y: auto;
|
||||
animation: slideUp 0.25s ease-out;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.modal-handle {
|
||||
width: 36px;
|
||||
height: 4px;
|
||||
|
|
@ -1824,34 +1812,6 @@ body {
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ===== Toast Notification ===== */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--text);
|
||||
color: var(--white);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
z-index: 300;
|
||||
animation: toastIn 0.3s, toastOut 0.3s 1.7s forwards;
|
||||
pointer-events: none;
|
||||
max-width: calc(100% - 32px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(10px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes toastOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ===== Utility ===== */
|
||||
.hidden {
|
||||
|
|
@ -1958,26 +1918,45 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
animation: slideDown 0.25s ease-in both;
|
||||
}
|
||||
|
||||
/* -- Tab content transition -- */
|
||||
/* -- Tab content transition — crossfade effect on switch only -- */
|
||||
.tab-content {
|
||||
animation: fadeIn 0.25s ease both;
|
||||
/* no default animation; applied via JS .switching class during tab switches */
|
||||
}
|
||||
.tab-content.switching {
|
||||
animation: fadeIn 0.2s ease both;
|
||||
animation: fadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
/* -- Page content transition on tab switch -- */
|
||||
.main-content.page-transition {
|
||||
animation: fadeIn 0.3s ease both;
|
||||
}
|
||||
|
||||
/* -- Staggered list fade-in -- */
|
||||
.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item {
|
||||
animation: fadeIn 0.3s ease both;
|
||||
.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item,
|
||||
.eetmoment-card, .eetmoment-item, .meal-add-option {
|
||||
animation: fadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
animation-delay: calc(var(--i, 0) * 50ms);
|
||||
}
|
||||
|
||||
/* -- Toast notification -- */
|
||||
.toast {
|
||||
animation: slideInRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
background: var(--text);
|
||||
color: var(--white);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
z-index: 300;
|
||||
animation: slideUp 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast.hiding {
|
||||
animation: slideOutRight 0.3s ease-in both;
|
||||
animation: slideDown 0.25s ease-in both;
|
||||
}
|
||||
|
||||
/* -- Stepper -- */
|
||||
|
|
@ -2782,7 +2761,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
|
||||
// Eating moments
|
||||
const listEl = document.getElementById('eetmomentList');
|
||||
listEl.innerHTML = EETMOMENTEN.map(moment => {
|
||||
listEl.innerHTML = EETMOMENTEN.map((moment, mi) => {
|
||||
const items = dayData[moment.id] || [];
|
||||
const momentKh = berekenEetmomentKh(activeDate, moment.id);
|
||||
const roundedKh = Math.round(momentKh);
|
||||
|
|
@ -2864,7 +2843,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
const cardOverLimit = overLimit && momentKh > 0;
|
||||
|
||||
return `
|
||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}">
|
||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}" style="--i:${mi}">
|
||||
<div class="eetmoment-header" data-moment="${moment.id}">
|
||||
<div class="eetmoment-header-left">
|
||||
<span class="eetmoment-icon">${moment.icon}</span>
|
||||
|
|
@ -3128,7 +3107,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
const countEl = stepper.querySelector('.portie-stepper-count');
|
||||
const grams = parseInt(stepper.dataset.grams, 10);
|
||||
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
// Reset all other steppers to 0
|
||||
steppers.forEach(s => {
|
||||
if (s !== stepper) { s.querySelector('.portie-stepper-count').textContent = '0'; }
|
||||
|
|
@ -3136,12 +3115,18 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
let count = parseInt(countEl.textContent, 10) || 0;
|
||||
countEl.textContent = count + 1;
|
||||
updatePortionFromSteppers();
|
||||
// Bounce feedback
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
let count = parseInt(countEl.textContent, 10) || 0;
|
||||
if (count > 0) countEl.textContent = count - 1;
|
||||
updatePortionFromSteppers();
|
||||
// Bounce feedback
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -3298,7 +3283,6 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function closeDialog() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
overlay.querySelector('#portieEditClose').addEventListener('click', closeDialog);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeDialog(); });
|
||||
|
||||
|
|
@ -3343,15 +3327,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
steppers.forEach(stepper => {
|
||||
const countEl = stepper.querySelector('.portie-stepper-count');
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
steppers.forEach(s => { if (s !== stepper) s.querySelector('.portie-stepper-count').textContent = '0'; });
|
||||
countEl.textContent = (parseInt(countEl.textContent, 10) || 0) + 1;
|
||||
updateStepperTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
const c = parseInt(countEl.textContent, 10) || 0;
|
||||
if (c > 0) countEl.textContent = c - 1;
|
||||
updateStepperTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -3478,7 +3466,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function closeDialog() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function closeDialog() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
overlay.querySelector('#bulkEditClose').addEventListener('click', closeDialog);
|
||||
overlay.querySelector('#bulkCancel').addEventListener('click', closeDialog);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeDialog(); });
|
||||
|
|
@ -3553,6 +3544,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.getElementById('navDagboek').classList.toggle('active', tab === 'dagboek');
|
||||
document.getElementById('navMaaltijden').classList.toggle('active', tab === 'maaltijden');
|
||||
|
||||
const mainContent = document.getElementById('mainContent');
|
||||
mainContent.classList.add('page-transition');
|
||||
setTimeout(() => mainContent.classList.remove('page-transition'), 350);
|
||||
|
||||
if (tab === 'zoeken') {
|
||||
renderZoeken();
|
||||
} else if (tab === 'dagboek') {
|
||||
|
|
@ -3876,7 +3871,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
listEl.innerHTML = maaltijden.map((meal, idx) => {
|
||||
const itemCount = meal.items.length;
|
||||
return `
|
||||
<div class="meal-card">
|
||||
<div class="meal-card" style="--i:${idx}">
|
||||
<div class="meal-card-main" data-idx="${idx}">
|
||||
<div class="meal-card-info">
|
||||
<div class="meal-card-name">${escapeHtml(meal.naam)}</div>
|
||||
|
|
@ -4079,7 +4074,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
function applyChanges(toPermanent) {
|
||||
const newItems = [];
|
||||
editItems.forEach((e, i) => {
|
||||
|
|
@ -4170,7 +4168,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
const input = overlay.querySelector('#nameInputField');
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
function submit() { const val = input.value.trim(); close(); onSave(val || null); }
|
||||
overlay.querySelector('#nameInputClose').addEventListener('click', close);
|
||||
overlay.querySelector('#nameInputCancel').addEventListener('click', close);
|
||||
|
|
@ -4231,7 +4232,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
|
||||
function save() {
|
||||
const newItems = [];
|
||||
|
|
@ -4381,7 +4385,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
`;
|
||||
document.body.appendChild(po);
|
||||
document.body.style.overflow = 'hidden';
|
||||
function closePicker() { po.remove(); document.body.style.overflow = ''; }
|
||||
function closePicker() {
|
||||
po.classList.add('closing');
|
||||
setTimeout(() => { po.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
po.querySelector('#mealPortionClose').addEventListener('click', closePicker);
|
||||
po.querySelector('#mealPortionCancel').addEventListener('click', closePicker);
|
||||
po.addEventListener('click', (e) => { if (e.target === po) closePicker(); });
|
||||
|
|
@ -4407,15 +4414,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
poSteppers.forEach(stepper => {
|
||||
const ce = stepper.querySelector('.portie-stepper-count');
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
poSteppers.forEach(s => { if (s !== stepper) s.querySelector('.portie-stepper-count').textContent = '0'; });
|
||||
ce.textContent = (parseInt(ce.textContent, 10) || 0) + 1;
|
||||
updatePoTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
const c = parseInt(ce.textContent, 10) || 0;
|
||||
if (c > 0) ce.textContent = c - 1;
|
||||
updatePoTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
po.querySelector('#mealPortionConfirm').addEventListener('click', () => {
|
||||
|
|
@ -4656,6 +4667,17 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
|
||||
// Start the app
|
||||
// Haptic feedback: vibrate on every button press (native Vibration API — Android only, iOS silently ignores)
|
||||
(function setupHaptics() {
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
|
||||
navigator.vibrate(8);
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
})();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
|
|
|
|||
146
index.html
146
index.html
|
|
@ -800,12 +800,6 @@ body {
|
|||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.2s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal-sheet {
|
||||
|
|
@ -816,15 +810,9 @@ body {
|
|||
max-height: 85vh;
|
||||
max-height: 85dvh;
|
||||
overflow-y: auto;
|
||||
animation: slideUp 0.25s ease-out;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.modal-handle {
|
||||
width: 36px;
|
||||
height: 4px;
|
||||
|
|
@ -1824,34 +1812,6 @@ body {
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ===== Toast Notification ===== */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--text);
|
||||
color: var(--white);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
z-index: 300;
|
||||
animation: toastIn 0.3s, toastOut 0.3s 1.7s forwards;
|
||||
pointer-events: none;
|
||||
max-width: calc(100% - 32px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(10px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes toastOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ===== Utility ===== */
|
||||
.hidden {
|
||||
|
|
@ -1958,26 +1918,45 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
animation: slideDown 0.25s ease-in both;
|
||||
}
|
||||
|
||||
/* -- Tab content transition -- */
|
||||
/* -- Tab content transition — crossfade effect on switch only -- */
|
||||
.tab-content {
|
||||
animation: fadeIn 0.25s ease both;
|
||||
/* no default animation; applied via JS .switching class during tab switches */
|
||||
}
|
||||
.tab-content.switching {
|
||||
animation: fadeIn 0.2s ease both;
|
||||
animation: fadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
/* -- Page content transition on tab switch -- */
|
||||
.main-content.page-transition {
|
||||
animation: fadeIn 0.3s ease both;
|
||||
}
|
||||
|
||||
/* -- Staggered list fade-in -- */
|
||||
.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item {
|
||||
animation: fadeIn 0.3s ease both;
|
||||
.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item,
|
||||
.eetmoment-card, .eetmoment-item, .meal-add-option {
|
||||
animation: fadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
animation-delay: calc(var(--i, 0) * 50ms);
|
||||
}
|
||||
|
||||
/* -- Toast notification -- */
|
||||
.toast {
|
||||
animation: slideInRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
background: var(--text);
|
||||
color: var(--white);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
z-index: 300;
|
||||
animation: slideUp 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast.hiding {
|
||||
animation: slideOutRight 0.3s ease-in both;
|
||||
animation: slideDown 0.25s ease-in both;
|
||||
}
|
||||
|
||||
/* -- Stepper -- */
|
||||
|
|
@ -2782,7 +2761,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
|
||||
// Eating moments
|
||||
const listEl = document.getElementById('eetmomentList');
|
||||
listEl.innerHTML = EETMOMENTEN.map(moment => {
|
||||
listEl.innerHTML = EETMOMENTEN.map((moment, mi) => {
|
||||
const items = dayData[moment.id] || [];
|
||||
const momentKh = berekenEetmomentKh(activeDate, moment.id);
|
||||
const roundedKh = Math.round(momentKh);
|
||||
|
|
@ -2864,7 +2843,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
const cardOverLimit = overLimit && momentKh > 0;
|
||||
|
||||
return `
|
||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}">
|
||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}" style="--i:${mi}">
|
||||
<div class="eetmoment-header" data-moment="${moment.id}">
|
||||
<div class="eetmoment-header-left">
|
||||
<span class="eetmoment-icon">${moment.icon}</span>
|
||||
|
|
@ -3128,7 +3107,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
const countEl = stepper.querySelector('.portie-stepper-count');
|
||||
const grams = parseInt(stepper.dataset.grams, 10);
|
||||
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
// Reset all other steppers to 0
|
||||
steppers.forEach(s => {
|
||||
if (s !== stepper) { s.querySelector('.portie-stepper-count').textContent = '0'; }
|
||||
|
|
@ -3136,12 +3115,18 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
let count = parseInt(countEl.textContent, 10) || 0;
|
||||
countEl.textContent = count + 1;
|
||||
updatePortionFromSteppers();
|
||||
// Bounce feedback
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
let count = parseInt(countEl.textContent, 10) || 0;
|
||||
if (count > 0) countEl.textContent = count - 1;
|
||||
updatePortionFromSteppers();
|
||||
// Bounce feedback
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -3298,7 +3283,6 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function closeDialog() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
overlay.querySelector('#portieEditClose').addEventListener('click', closeDialog);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeDialog(); });
|
||||
|
||||
|
|
@ -3343,15 +3327,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
steppers.forEach(stepper => {
|
||||
const countEl = stepper.querySelector('.portie-stepper-count');
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
steppers.forEach(s => { if (s !== stepper) s.querySelector('.portie-stepper-count').textContent = '0'; });
|
||||
countEl.textContent = (parseInt(countEl.textContent, 10) || 0) + 1;
|
||||
updateStepperTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
const c = parseInt(countEl.textContent, 10) || 0;
|
||||
if (c > 0) countEl.textContent = c - 1;
|
||||
updateStepperTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -3478,7 +3466,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function closeDialog() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function closeDialog() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
overlay.querySelector('#bulkEditClose').addEventListener('click', closeDialog);
|
||||
overlay.querySelector('#bulkCancel').addEventListener('click', closeDialog);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeDialog(); });
|
||||
|
|
@ -3553,6 +3544,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.getElementById('navDagboek').classList.toggle('active', tab === 'dagboek');
|
||||
document.getElementById('navMaaltijden').classList.toggle('active', tab === 'maaltijden');
|
||||
|
||||
const mainContent = document.getElementById('mainContent');
|
||||
mainContent.classList.add('page-transition');
|
||||
setTimeout(() => mainContent.classList.remove('page-transition'), 350);
|
||||
|
||||
if (tab === 'zoeken') {
|
||||
renderZoeken();
|
||||
} else if (tab === 'dagboek') {
|
||||
|
|
@ -3876,7 +3871,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
listEl.innerHTML = maaltijden.map((meal, idx) => {
|
||||
const itemCount = meal.items.length;
|
||||
return `
|
||||
<div class="meal-card">
|
||||
<div class="meal-card" style="--i:${idx}">
|
||||
<div class="meal-card-main" data-idx="${idx}">
|
||||
<div class="meal-card-info">
|
||||
<div class="meal-card-name">${escapeHtml(meal.naam)}</div>
|
||||
|
|
@ -4079,7 +4074,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
function applyChanges(toPermanent) {
|
||||
const newItems = [];
|
||||
editItems.forEach((e, i) => {
|
||||
|
|
@ -4170,7 +4168,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
const input = overlay.querySelector('#nameInputField');
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
function submit() { const val = input.value.trim(); close(); onSave(val || null); }
|
||||
overlay.querySelector('#nameInputClose').addEventListener('click', close);
|
||||
overlay.querySelector('#nameInputCancel').addEventListener('click', close);
|
||||
|
|
@ -4231,7 +4232,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
function close() { overlay.remove(); document.body.style.overflow = ''; }
|
||||
function close() {
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(() => { overlay.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
|
||||
function save() {
|
||||
const newItems = [];
|
||||
|
|
@ -4381,7 +4385,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
`;
|
||||
document.body.appendChild(po);
|
||||
document.body.style.overflow = 'hidden';
|
||||
function closePicker() { po.remove(); document.body.style.overflow = ''; }
|
||||
function closePicker() {
|
||||
po.classList.add('closing');
|
||||
setTimeout(() => { po.remove(); document.body.style.overflow = ''; }, 280);
|
||||
}
|
||||
po.querySelector('#mealPortionClose').addEventListener('click', closePicker);
|
||||
po.querySelector('#mealPortionCancel').addEventListener('click', closePicker);
|
||||
po.addEventListener('click', (e) => { if (e.target === po) closePicker(); });
|
||||
|
|
@ -4407,15 +4414,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
poSteppers.forEach(stepper => {
|
||||
const ce = stepper.querySelector('.portie-stepper-count');
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-plus').addEventListener('click', function() {
|
||||
poSteppers.forEach(s => { if (s !== stepper) s.querySelector('.portie-stepper-count').textContent = '0'; });
|
||||
ce.textContent = (parseInt(ce.textContent, 10) || 0) + 1;
|
||||
updatePoTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', () => {
|
||||
stepper.querySelector('.portie-stepper-minus').addEventListener('click', function() {
|
||||
const c = parseInt(ce.textContent, 10) || 0;
|
||||
if (c > 0) ce.textContent = c - 1;
|
||||
updatePoTotal();
|
||||
this.classList.add('tapped');
|
||||
setTimeout(() => this.classList.remove('tapped'), 300);
|
||||
});
|
||||
});
|
||||
po.querySelector('#mealPortionConfirm').addEventListener('click', () => {
|
||||
|
|
@ -4656,6 +4667,17 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
|
||||
// Start the app
|
||||
// Haptic feedback: vibrate on every button press (native Vibration API — Android only, iOS silently ignores)
|
||||
(function setupHaptics() {
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
|
||||
navigator.vibrate(8);
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
})();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ test.describe('Karby Eetdagboek', () => {
|
|||
await page.waitForTimeout(800);
|
||||
await page.locator('.food-item').first().click();
|
||||
await page.waitForTimeout(500);
|
||||
// Detail modal with Add button
|
||||
await expect(page.locator('#detailAddBtn')).toBeVisible({ timeout: 3000 });
|
||||
});
|
||||
|
||||
|
|
@ -55,11 +54,9 @@ test.describe('Karby Eetdagboek', () => {
|
|||
|
||||
test('settings modal opens', async ({ page }) => {
|
||||
await page.goto(BASE);
|
||||
// Click settings gear icon
|
||||
await page.locator('#settingsBtn').click();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(page.locator('#settingsModal')).not.toHaveClass(/hidden/, { timeout: 3000 });
|
||||
// Close
|
||||
await page.locator('#settingsModal .modal-close').click();
|
||||
await expect(page.locator('#settingsModal')).toHaveClass(/hidden/, { timeout: 2000 });
|
||||
});
|
||||
|
|
@ -73,7 +70,6 @@ test.describe('Karby Eetdagboek', () => {
|
|||
|
||||
test('contrast: key elements have visible colors', async ({ page }) => {
|
||||
await page.goto(BASE);
|
||||
// Nav buttons should have visible text color (not transparent)
|
||||
const navBtns = page.locator('.nav-btn');
|
||||
const count = await navBtns.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
|
|
@ -83,4 +79,313 @@ test.describe('Karby Eetdagboek', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// ========== 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);
|
||||
// Open add-to-meal modal (hier zitten de steppers)
|
||||
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);
|
||||
// Eerste stepper heeft count=1
|
||||
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; }
|
||||
// + op tweede stepper
|
||||
await steppers.nth(1).locator('.portie-stepper-plus').click();
|
||||
await page.waitForTimeout(100);
|
||||
// Eerste moet 0 zijn (gereset)
|
||||
await expect(steppers.first().locator('.portie-stepper-count')).toHaveText('0');
|
||||
// Tweede moet 1 zijn
|
||||
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();
|
||||
// Default count = 1
|
||||
await expect(firstStepper.locator('.portie-stepper-count')).toHaveText('1');
|
||||
// Minus klikken → 0
|
||||
await firstStepper.locator('.portie-stepper-minus').click();
|
||||
await expect(firstStepper.locator('.portie-stepper-count')).toHaveText('0');
|
||||
// Minus moet nu disabled zijn (niet negatief)
|
||||
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 is pas aanwezig na updatePortionFromSteppers (triggered door +/-)
|
||||
// Eerst: plus klikken om active te krijgen
|
||||
await firstStepper.locator('.portie-stepper-plus').click();
|
||||
await page.waitForTimeout(100);
|
||||
await expect(firstStepper).toHaveClass(/active/);
|
||||
// Minus naar 0 → active verliezen
|
||||
await firstStepper.locator('.portie-stepper-minus').click();
|
||||
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/);
|
||||
// .modal-overlay heeft animation: fadeIn
|
||||
const overlayAnim = await overlay.evaluate(el => getComputedStyle(el).animationName);
|
||||
expect(overlayAnim).toBe('fadeIn');
|
||||
// .modal-sheet heeft animation: slideUp
|
||||
const sheet = overlay.locator('.modal-sheet');
|
||||
const sheetAnim = await sheet.evaluate(el => getComputedStyle(el).animationName);
|
||||
expect(sheetAnim).toBe('slideUp');
|
||||
// Sluiten voor opschoning
|
||||
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);
|
||||
// Zoek + open detail
|
||||
await page.locator('.search-input').fill('brood');
|
||||
await page.waitForTimeout(800);
|
||||
await page.locator('.food-item').first().click();
|
||||
await page.waitForTimeout(500);
|
||||
// Toevoegen aan eetmoment
|
||||
await page.locator('#detailAddBtn').click();
|
||||
await page.waitForTimeout(500);
|
||||
// Terug naar dagboek (sluit addMealModal eerst)
|
||||
await page.locator('#addMealModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Sluit detail modal
|
||||
await page.locator('#detailModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Naar dagboek tab
|
||||
await page.locator('#navDagboek').click();
|
||||
await page.waitForTimeout(600);
|
||||
// Zoek save-meal btn en klik
|
||||
const saveBtn = page.locator('.save-meal-btn');
|
||||
if (await saveBtn.count() === 0) {
|
||||
test.skip('geen items in dagboek');
|
||||
return;
|
||||
}
|
||||
await saveBtn.first().click();
|
||||
await page.waitForTimeout(500);
|
||||
const nameInput = page.locator('#mealNameInput');
|
||||
await expect(nameInput).toBeVisible({ timeout: 5000 });
|
||||
// Naamveld moet leeg zijn
|
||||
await expect(nameInput).toHaveValue('');
|
||||
// Annuleren
|
||||
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);
|
||||
// Zoek + open detail
|
||||
await page.locator('.search-input').fill('brood');
|
||||
await page.waitForTimeout(800);
|
||||
await page.locator('.food-item').first().click();
|
||||
await page.waitForTimeout(500);
|
||||
// Toevoegen aan eetmoment
|
||||
await page.locator('#detailAddBtn').click();
|
||||
await page.waitForTimeout(500);
|
||||
// Sluit addMealModal
|
||||
await page.locator('#addMealModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Sluit detail modal
|
||||
await page.locator('#detailModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Naar dagboek
|
||||
await page.locator('#navDagboek').click();
|
||||
await page.waitForTimeout(600);
|
||||
// Opslaan als maaltijd
|
||||
const saveBtn = page.locator('.save-meal-btn');
|
||||
if (await saveBtn.count() === 0) {
|
||||
test.skip('geen items in dagboek');
|
||||
return;
|
||||
}
|
||||
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
|
||||
await page.locator('#mealNameSave').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Modal moet nog open zijn (niet gesloten)
|
||||
await expect(nameInput).toBeVisible();
|
||||
// Input border moet rood zijn (validation)
|
||||
const borderColor = await nameInput.evaluate(el => getComputedStyle(el).borderColor);
|
||||
const rgb = borderColor.match(/\d+/g).map(Number);
|
||||
expect(rgb[0]).toBeGreaterThan(150); // R > 150
|
||||
expect(rgb[1]).toBeLessThan(100); // G < 100
|
||||
expect(rgb[2]).toBeLessThan(100); // B < 100
|
||||
// Annuleren
|
||||
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();
|
||||
// Next day (tweede .day-nav-btn)
|
||||
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);
|
||||
// Today reset
|
||||
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
|
||||
await page.locator('.search-input').fill('brood');
|
||||
await page.waitForTimeout(800);
|
||||
await page.locator('.food-item').first().click();
|
||||
await page.waitForTimeout(500);
|
||||
// Stap 2: Voeg toe aan eetmoment
|
||||
await page.locator('#detailAddBtn').click();
|
||||
await page.waitForTimeout(500);
|
||||
// Sluit addMealModal
|
||||
await page.locator('#addMealModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Sluit detail modal (anders overlay intercepteert nav knoppen)
|
||||
await page.locator('#detailModal .modal-close').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Stap 3: Naar dagboek
|
||||
await page.locator('#navDagboek').click();
|
||||
await page.waitForTimeout(600);
|
||||
// Stap 4: Opslaan als maaltijd
|
||||
const saveBtn = page.locator('.save-meal-btn');
|
||||
if (await saveBtn.count() === 0) {
|
||||
test.skip('geen items in dagboek — kan geen maaltijd opslaan');
|
||||
return;
|
||||
}
|
||||
await saveBtn.first().click();
|
||||
await page.waitForTimeout(500);
|
||||
// Stap 5: Naam invullen en opslaan
|
||||
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);
|
||||
// De meal wordt opgeslagen, toast verschijnt
|
||||
// Stap 6: Ga terug naar dagboek om meal-group te zien
|
||||
await page.locator('#navDagboek').click();
|
||||
await page.waitForTimeout(800);
|
||||
// Stap 7: Zoek de meal-group
|
||||
const mealGroup = page.locator('.eetmoment-meal-group').first();
|
||||
await expect(mealGroup).toBeVisible({ timeout: 5000 });
|
||||
// Stap 8: Items-wrap begint collapsed (hidden class)
|
||||
const itemsWrap = mealGroup.locator('.eetmoment-meal-items-wrap');
|
||||
await expect(itemsWrap).toHaveClass(/hidden/);
|
||||
// Stap 9: Klik op 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/);
|
||||
// Chevron moet naar boven wijzen
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue