UX — open tussendoortjessuggesties als bottom sheet
- Verplaatst de bestaande tabTussendoortjes-inhoud naar een nieuwe inspiratieSheet bottom-sheet overlay (reuseert .modal-overlay, .modal-sheet, .modal-handle, backdrop-close en setupSwipeDown). - Klikken op 'Vind inspiratie' bij Tussendoor 1/2/3 opent nu de bottom sheet i.p.v. volledige tab-navigatie. - Sheet heeft X-knop, backdrop-click en swipe-down om te sluiten. - Haptics bij openen (10ms) en sluiten (6ms). - renderTussendoortjes() werkt zowel in tab als sheet context. - clearInspiratieContext() herstelt context ook als sheet open is. - Playwright-tests geüpdatet: verifiëren sheet open/sluit, titel, context, X/backdrop close, Dagboek blijft actief, geen tab-navigatie. - docker/index.html gesynchroniseerd.
This commit is contained in:
parent
3422b09214
commit
85ef8ba2de
4 changed files with 467 additions and 172 deletions
|
|
@ -2840,46 +2840,8 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tussendoortjes Tab -->
|
||||
<!-- Tussendoortjes Tab (leeg — content verplaatst naar inspiratieSheet bottom sheet) -->
|
||||
<div id="tabTussendoortjes" class="hidden">
|
||||
<div class="tussendoortjes-section">
|
||||
<h2 class="tussendoortjes-header" id="tussendoortjesTitle">Tussendoortjes</h2>
|
||||
|
||||
<div class="inspiratie-context hidden" id="inspiratieContext">
|
||||
<span class="inspiratie-context-label" id="inspiratieContextLabel"></span>
|
||||
<button class="inspiratie-context-close" id="inspiratieContextClose" aria-label="Sluit context" data-haptic="normal">×</button>
|
||||
</div>
|
||||
|
||||
<p class="tussendoortjes-sub">Lekkere, koolhydraatarme snacks binnen jouw gekozen bereik</p>
|
||||
|
||||
<div class="carb-range-control" id="carbRangeControl">
|
||||
<div class="carb-range-label">
|
||||
<span>Koolhydraten per portie</span>
|
||||
</div>
|
||||
<div class="range-inputs">
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMin">Min (g)</label>
|
||||
<input type="number" id="rangeMin" value="15" min="0" max="50" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMax">Max (g)</label>
|
||||
<input type="number" id="rangeMax" value="20" min="1" max="100" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="suggestieList"></div>
|
||||
|
||||
<div class="nieuwe-suggesties-btn-wrap">
|
||||
<button class="nieuwe-suggesties-btn" id="nieuweSuggestiesBtn">
|
||||
<i class="fas fa-rotate"></i> Nieuwe suggesties
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="suggestie-warning">
|
||||
<strong>Belangrijk:</strong> De getoonde voedingswaarden zijn gebaseerd op de NEVO-database (RIVM) en kunnen afwijken van merkproducten. Controleer altijd het etiket van jouw eigen producten. Deze suggesties zijn geen medisch advies en vervangen geen begeleiding van een diëtist of arts.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
@ -2946,6 +2908,55 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inspiratie Bottom Sheet -->
|
||||
<div class="modal-overlay hidden" id="inspiratieSheet">
|
||||
<div class="modal-sheet">
|
||||
<div class="modal-handle"></div>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" id="inspiratieSheetTitle">Tussendoortjes</span>
|
||||
<button class="modal-close" id="inspiratieSheetClose"><i class="fas fa-xmark"></i></button>
|
||||
</div>
|
||||
<div class="modal-body" id="inspiratieSheetBody">
|
||||
<div class="tussendoortjes-section" style="padding:0;">
|
||||
<div class="inspiratie-context hidden" id="inspiratieContext">
|
||||
<span class="inspiratie-context-label" id="inspiratieContextLabel"></span>
|
||||
<button class="inspiratie-context-close" id="inspiratieContextClose" aria-label="Sluit context" data-haptic="normal">×</button>
|
||||
</div>
|
||||
|
||||
<p class="tussendoortjes-sub">Lekkere, koolhydraatarme snacks binnen jouw gekozen bereik</p>
|
||||
|
||||
<div class="carb-range-control" id="carbRangeControl">
|
||||
<div class="carb-range-label">
|
||||
<span>Koolhydraten per portie</span>
|
||||
</div>
|
||||
<div class="range-inputs">
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMin">Min (g)</label>
|
||||
<input type="number" id="rangeMin" value="15" min="0" max="50" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMax">Max (g)</label>
|
||||
<input type="number" id="rangeMax" value="20" min="1" max="100" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="suggestieList"></div>
|
||||
|
||||
<div class="nieuwe-suggesties-btn-wrap">
|
||||
<button class="nieuwe-suggesties-btn" id="nieuweSuggestiesBtn">
|
||||
<i class="fas fa-rotate"></i> Nieuwe suggesties
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="suggestie-warning">
|
||||
<strong>Belangrijk:</strong> De getoonde voedingswaarden zijn gebaseerd op de NEVO-database (RIVM) en kunnen afwijken van merkproducten. Controleer altijd het etiket van jouw eigen producten. Deze suggesties zijn geen medisch advies en vervangen geen begeleiding van een diëtist of arts.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toastContainer"></div>
|
||||
|
||||
|
|
@ -6022,21 +6033,27 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
|
||||
function renderTussendoortjes() {
|
||||
if (activeTab !== 'tussendoortjes') return;
|
||||
if (activeTab !== 'tussendoortjes') {
|
||||
var sheet = document.getElementById('inspiratieSheet');
|
||||
var isSheetOpen = sheet && !sheet.classList.contains('hidden') && !sheet.classList.contains('closing');
|
||||
if (!isSheetOpen) return;
|
||||
}
|
||||
|
||||
const listEl = document.getElementById('suggestieList');
|
||||
if (!listEl) return;
|
||||
|
||||
const titleEl = document.getElementById('tussendoortjesTitle');
|
||||
const sheetTitleEl = document.getElementById('inspiratieSheetTitle');
|
||||
const contextEl = document.getElementById('inspiratieContext');
|
||||
const contextLabel = document.getElementById('inspiratieContextLabel');
|
||||
|
||||
// Update title and context based on inspiration mode
|
||||
if (_inspiratieMoment && titleEl) {
|
||||
titleEl.textContent = 'Inspiratie voor ' + _inspiratieMoment;
|
||||
} else {
|
||||
titleEl.textContent = 'Tussendoortjes';
|
||||
var titleText = 'Tussendoortjes';
|
||||
if (_inspiratieMoment) {
|
||||
titleText = 'Inspiratie voor ' + _inspiratieMoment;
|
||||
}
|
||||
if (titleEl) titleEl.textContent = titleText;
|
||||
if (sheetTitleEl) sheetTitleEl.textContent = titleText;
|
||||
|
||||
// Show/hide context header
|
||||
if (contextEl) {
|
||||
|
|
@ -6129,7 +6146,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
_inspiratieMoment = momentId;
|
||||
_inspiratieDate = dateStr;
|
||||
_cachedGeneratedSet = null;
|
||||
switchTab('tussendoortjes');
|
||||
openInspiratieSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -6139,9 +6156,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
_inspiratieMoment = null;
|
||||
_inspiratieDate = null;
|
||||
_cachedGeneratedSet = null;
|
||||
// Re-render if on tussendoortjes tab
|
||||
// Re-render if on tussendoortjes tab or if sheet is open
|
||||
if (activeTab === 'tussendoortjes') {
|
||||
renderTussendoortjes();
|
||||
} else {
|
||||
var sheet = document.getElementById('inspiratieSheet');
|
||||
var isSheetOpen = sheet && !sheet.classList.contains('hidden') && !sheet.classList.contains('closing');
|
||||
if (isSheetOpen) {
|
||||
renderTussendoortjes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6266,6 +6289,44 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}, { passive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the inspiratie bottom sheet and render suggestions.
|
||||
*/
|
||||
function openInspiratieSheet() {
|
||||
var overlay = document.getElementById('inspiratieSheet');
|
||||
if (!overlay) return;
|
||||
overlay.classList.remove('hidden', 'closing');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Render tussendoortjes content in the sheet
|
||||
renderTussendoortjes();
|
||||
|
||||
// Haptic feedback
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
navigator.vibrate(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the inspiratie bottom sheet and return to dagboek context.
|
||||
*/
|
||||
function closeInspiratieSheet() {
|
||||
var overlay = document.getElementById('inspiratieSheet');
|
||||
if (!overlay) return;
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(function() {
|
||||
overlay.classList.add('hidden');
|
||||
overlay.classList.remove('closing');
|
||||
document.body.style.overflow = '';
|
||||
clearInspiratieContext();
|
||||
}, 280);
|
||||
|
||||
// Haptic feedback
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
navigator.vibrate(6);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const container = document.getElementById('toastContainer');
|
||||
const toast = document.createElement('div');
|
||||
|
|
@ -6844,6 +6905,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.getElementById('detailModalClose').addEventListener('click', () => closeModal('detailModal'));
|
||||
document.getElementById('addMealModalClose').addEventListener('click', () => closeModal('addMealModal'));
|
||||
|
||||
// Inspiratie sheet close
|
||||
var inspiratieSheetClose = document.getElementById('inspiratieSheetClose');
|
||||
if (inspiratieSheetClose) inspiratieSheetClose.addEventListener('click', closeInspiratieSheet);
|
||||
|
||||
// Close modals on overlay click
|
||||
document.getElementById('detailModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('detailModal')) closeModal('detailModal');
|
||||
|
|
@ -6852,6 +6917,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
if (e.target === document.getElementById('addMealModal')) closeModal('addMealModal');
|
||||
});
|
||||
|
||||
// Inspiratie sheet backdrop close + swipe-down
|
||||
var inspiratieSheet = document.getElementById('inspiratieSheet');
|
||||
if (inspiratieSheet) {
|
||||
inspiratieSheet.addEventListener('click', function(e) {
|
||||
if (e.target === inspiratieSheet) closeInspiratieSheet();
|
||||
});
|
||||
setupSwipeDown(inspiratieSheet, closeInspiratieSheet);
|
||||
}
|
||||
|
||||
// Swipe-down to close for static modals
|
||||
setupSwipeDown(document.getElementById('detailModal'), () => closeModal('detailModal'));
|
||||
setupSwipeDown(document.getElementById('addMealModal'), () => closeModal('addMealModal'));
|
||||
|
|
|
|||
166
index.html
166
index.html
|
|
@ -2840,46 +2840,8 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tussendoortjes Tab -->
|
||||
<!-- Tussendoortjes Tab (leeg — content verplaatst naar inspiratieSheet bottom sheet) -->
|
||||
<div id="tabTussendoortjes" class="hidden">
|
||||
<div class="tussendoortjes-section">
|
||||
<h2 class="tussendoortjes-header" id="tussendoortjesTitle">Tussendoortjes</h2>
|
||||
|
||||
<div class="inspiratie-context hidden" id="inspiratieContext">
|
||||
<span class="inspiratie-context-label" id="inspiratieContextLabel"></span>
|
||||
<button class="inspiratie-context-close" id="inspiratieContextClose" aria-label="Sluit context" data-haptic="normal">×</button>
|
||||
</div>
|
||||
|
||||
<p class="tussendoortjes-sub">Lekkere, koolhydraatarme snacks binnen jouw gekozen bereik</p>
|
||||
|
||||
<div class="carb-range-control" id="carbRangeControl">
|
||||
<div class="carb-range-label">
|
||||
<span>Koolhydraten per portie</span>
|
||||
</div>
|
||||
<div class="range-inputs">
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMin">Min (g)</label>
|
||||
<input type="number" id="rangeMin" value="15" min="0" max="50" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMax">Max (g)</label>
|
||||
<input type="number" id="rangeMax" value="20" min="1" max="100" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="suggestieList"></div>
|
||||
|
||||
<div class="nieuwe-suggesties-btn-wrap">
|
||||
<button class="nieuwe-suggesties-btn" id="nieuweSuggestiesBtn">
|
||||
<i class="fas fa-rotate"></i> Nieuwe suggesties
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="suggestie-warning">
|
||||
<strong>Belangrijk:</strong> De getoonde voedingswaarden zijn gebaseerd op de NEVO-database (RIVM) en kunnen afwijken van merkproducten. Controleer altijd het etiket van jouw eigen producten. Deze suggesties zijn geen medisch advies en vervangen geen begeleiding van een diëtist of arts.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
@ -2946,6 +2908,55 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inspiratie Bottom Sheet -->
|
||||
<div class="modal-overlay hidden" id="inspiratieSheet">
|
||||
<div class="modal-sheet">
|
||||
<div class="modal-handle"></div>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" id="inspiratieSheetTitle">Tussendoortjes</span>
|
||||
<button class="modal-close" id="inspiratieSheetClose"><i class="fas fa-xmark"></i></button>
|
||||
</div>
|
||||
<div class="modal-body" id="inspiratieSheetBody">
|
||||
<div class="tussendoortjes-section" style="padding:0;">
|
||||
<div class="inspiratie-context hidden" id="inspiratieContext">
|
||||
<span class="inspiratie-context-label" id="inspiratieContextLabel"></span>
|
||||
<button class="inspiratie-context-close" id="inspiratieContextClose" aria-label="Sluit context" data-haptic="normal">×</button>
|
||||
</div>
|
||||
|
||||
<p class="tussendoortjes-sub">Lekkere, koolhydraatarme snacks binnen jouw gekozen bereik</p>
|
||||
|
||||
<div class="carb-range-control" id="carbRangeControl">
|
||||
<div class="carb-range-label">
|
||||
<span>Koolhydraten per portie</span>
|
||||
</div>
|
||||
<div class="range-inputs">
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMin">Min (g)</label>
|
||||
<input type="number" id="rangeMin" value="15" min="0" max="50" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
<div class="range-input-group">
|
||||
<label for="rangeMax">Max (g)</label>
|
||||
<input type="number" id="rangeMax" value="20" min="1" max="100" inputmode="numeric" data-haptic="normal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="suggestieList"></div>
|
||||
|
||||
<div class="nieuwe-suggesties-btn-wrap">
|
||||
<button class="nieuwe-suggesties-btn" id="nieuweSuggestiesBtn">
|
||||
<i class="fas fa-rotate"></i> Nieuwe suggesties
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="suggestie-warning">
|
||||
<strong>Belangrijk:</strong> De getoonde voedingswaarden zijn gebaseerd op de NEVO-database (RIVM) en kunnen afwijken van merkproducten. Controleer altijd het etiket van jouw eigen producten. Deze suggesties zijn geen medisch advies en vervangen geen begeleiding van een diëtist of arts.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toastContainer"></div>
|
||||
|
||||
|
|
@ -6022,21 +6033,27 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}
|
||||
|
||||
function renderTussendoortjes() {
|
||||
if (activeTab !== 'tussendoortjes') return;
|
||||
if (activeTab !== 'tussendoortjes') {
|
||||
var sheet = document.getElementById('inspiratieSheet');
|
||||
var isSheetOpen = sheet && !sheet.classList.contains('hidden') && !sheet.classList.contains('closing');
|
||||
if (!isSheetOpen) return;
|
||||
}
|
||||
|
||||
const listEl = document.getElementById('suggestieList');
|
||||
if (!listEl) return;
|
||||
|
||||
const titleEl = document.getElementById('tussendoortjesTitle');
|
||||
const sheetTitleEl = document.getElementById('inspiratieSheetTitle');
|
||||
const contextEl = document.getElementById('inspiratieContext');
|
||||
const contextLabel = document.getElementById('inspiratieContextLabel');
|
||||
|
||||
// Update title and context based on inspiration mode
|
||||
if (_inspiratieMoment && titleEl) {
|
||||
titleEl.textContent = 'Inspiratie voor ' + _inspiratieMoment;
|
||||
} else {
|
||||
titleEl.textContent = 'Tussendoortjes';
|
||||
var titleText = 'Tussendoortjes';
|
||||
if (_inspiratieMoment) {
|
||||
titleText = 'Inspiratie voor ' + _inspiratieMoment;
|
||||
}
|
||||
if (titleEl) titleEl.textContent = titleText;
|
||||
if (sheetTitleEl) sheetTitleEl.textContent = titleText;
|
||||
|
||||
// Show/hide context header
|
||||
if (contextEl) {
|
||||
|
|
@ -6129,7 +6146,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
_inspiratieMoment = momentId;
|
||||
_inspiratieDate = dateStr;
|
||||
_cachedGeneratedSet = null;
|
||||
switchTab('tussendoortjes');
|
||||
openInspiratieSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -6139,9 +6156,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
_inspiratieMoment = null;
|
||||
_inspiratieDate = null;
|
||||
_cachedGeneratedSet = null;
|
||||
// Re-render if on tussendoortjes tab
|
||||
// Re-render if on tussendoortjes tab or if sheet is open
|
||||
if (activeTab === 'tussendoortjes') {
|
||||
renderTussendoortjes();
|
||||
} else {
|
||||
var sheet = document.getElementById('inspiratieSheet');
|
||||
var isSheetOpen = sheet && !sheet.classList.contains('hidden') && !sheet.classList.contains('closing');
|
||||
if (isSheetOpen) {
|
||||
renderTussendoortjes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6266,6 +6289,44 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
}, { passive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the inspiratie bottom sheet and render suggestions.
|
||||
*/
|
||||
function openInspiratieSheet() {
|
||||
var overlay = document.getElementById('inspiratieSheet');
|
||||
if (!overlay) return;
|
||||
overlay.classList.remove('hidden', 'closing');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Render tussendoortjes content in the sheet
|
||||
renderTussendoortjes();
|
||||
|
||||
// Haptic feedback
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
navigator.vibrate(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the inspiratie bottom sheet and return to dagboek context.
|
||||
*/
|
||||
function closeInspiratieSheet() {
|
||||
var overlay = document.getElementById('inspiratieSheet');
|
||||
if (!overlay) return;
|
||||
overlay.classList.add('closing');
|
||||
setTimeout(function() {
|
||||
overlay.classList.add('hidden');
|
||||
overlay.classList.remove('closing');
|
||||
document.body.style.overflow = '';
|
||||
clearInspiratieContext();
|
||||
}, 280);
|
||||
|
||||
// Haptic feedback
|
||||
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
||||
navigator.vibrate(6);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const container = document.getElementById('toastContainer');
|
||||
const toast = document.createElement('div');
|
||||
|
|
@ -6844,6 +6905,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
document.getElementById('detailModalClose').addEventListener('click', () => closeModal('detailModal'));
|
||||
document.getElementById('addMealModalClose').addEventListener('click', () => closeModal('addMealModal'));
|
||||
|
||||
// Inspiratie sheet close
|
||||
var inspiratieSheetClose = document.getElementById('inspiratieSheetClose');
|
||||
if (inspiratieSheetClose) inspiratieSheetClose.addEventListener('click', closeInspiratieSheet);
|
||||
|
||||
// Close modals on overlay click
|
||||
document.getElementById('detailModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('detailModal')) closeModal('detailModal');
|
||||
|
|
@ -6852,6 +6917,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
|||
if (e.target === document.getElementById('addMealModal')) closeModal('addMealModal');
|
||||
});
|
||||
|
||||
// Inspiratie sheet backdrop close + swipe-down
|
||||
var inspiratieSheet = document.getElementById('inspiratieSheet');
|
||||
if (inspiratieSheet) {
|
||||
inspiratieSheet.addEventListener('click', function(e) {
|
||||
if (e.target === inspiratieSheet) closeInspiratieSheet();
|
||||
});
|
||||
setupSwipeDown(inspiratieSheet, closeInspiratieSheet);
|
||||
}
|
||||
|
||||
// Swipe-down to close for static modals
|
||||
setupSwipeDown(document.getElementById('detailModal'), () => closeModal('detailModal'));
|
||||
setupSwipeDown(document.getElementById('addMealModal'), () => closeModal('addMealModal'));
|
||||
|
|
|
|||
|
|
@ -578,29 +578,41 @@ test.describe('Karby Eetdagboek', () => {
|
|||
|
||||
// ========== TUSSENDOORTJES (via inspiratie-flow, nav-knop verwijderd) ==========
|
||||
|
||||
test('tussendoortjes tab: bottom-nav button is removed, tab accessible via switchTab', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: bottom sheet opent via Vind inspiratie vanuit elk Tussendoor-moment', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1000);
|
||||
// Verify the bottom-nav button is NOT present
|
||||
await expect(page.locator('#navTussendoortjes')).not.toBeAttached();
|
||||
// Verify the tab content still exists
|
||||
await expect(page.locator('#tabTussendoortjes')).toBeAttached();
|
||||
// Navigate via page.evaluate (simulating Vind inspiratie flow)
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.waitForTimeout(500);
|
||||
await expect(page.locator('#tabTussendoortjes')).toBeVisible();
|
||||
await expect(page.locator('.tussendoortjes-header')).toHaveText('Tussendoortjes');
|
||||
await expect(page.locator('#rangeMin')).toBeVisible();
|
||||
await expect(page.locator('#rangeMax')).toBeVisible();
|
||||
// Open dagboek
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
// Click first Vind inspiratie button
|
||||
const btn = page.locator('.vind-inspiratie-btn').first();
|
||||
await expect(btn).toBeVisible();
|
||||
await btn.click();
|
||||
await page.waitForTimeout(600);
|
||||
// Sheet should open
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
await expect(page.locator('#inspiratieSheet')).not.toHaveClass(/hidden/);
|
||||
// Sheet title shows inspiration context
|
||||
await expect(page.locator('#inspiratieSheetTitle')).toContainText('Inspiratie voor');
|
||||
// Range controls are in the sheet
|
||||
await expect(page.locator('#inspiratieSheet #rangeMin')).toBeVisible();
|
||||
await expect(page.locator('#inspiratieSheet #rangeMax')).toBeVisible();
|
||||
// Dagboek stays active (no tab switch)
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: default range shows suggestie cards', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: default range shows suggestie cards in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
// Click first Vind inspiratie to open sheet
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(800);
|
||||
// Default range is 15-20g — should show at least one suggestie card
|
||||
const cards = page.locator('.suggestie-card');
|
||||
const cards = page.locator('#inspiratieSheet .suggestie-card');
|
||||
const count = await cards.count();
|
||||
expect(count).toBeGreaterThanOrEqual(1);
|
||||
// Each card should have a name, total, ingredients, and preparation
|
||||
|
|
@ -611,21 +623,23 @@ test.describe('Karby Eetdagboek', () => {
|
|||
await expect(firstCard.locator('.suggestie-bereiding')).toBeVisible();
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: filter by range shows only matching totals', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: filter by range in sheet shows only matching totals', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// Set range to 10-15g — should show suggestions within this range
|
||||
await page.locator('#rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMax').fill('15');
|
||||
await page.locator('#rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMin').fill('10');
|
||||
await page.locator('#inspiratieSheet #rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMax').fill('15');
|
||||
await page.locator('#inspiratieSheet #rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMin').fill('10');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check that all visible totals are within 10-15g
|
||||
const totals = page.locator('.suggestie-card-totaal');
|
||||
const totals = page.locator('#inspiratieSheet .suggestie-card-totaal');
|
||||
const totalCount = await totals.count();
|
||||
for (let i = 0; i < totalCount; i++) {
|
||||
const text = await totals.nth(i).textContent();
|
||||
|
|
@ -635,46 +649,52 @@ test.describe('Karby Eetdagboek', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: narrow range shows empty state', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: narrow range shows empty state in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// Set range to 0-1g — should show no suggestions
|
||||
await page.locator('#rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMax').fill('1');
|
||||
await page.locator('#rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMin').fill('0');
|
||||
await page.locator('#inspiratieSheet #rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMax').fill('1');
|
||||
await page.locator('#inspiratieSheet #rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMin').fill('0');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should show empty state
|
||||
const cards = page.locator('.suggestie-card');
|
||||
const cards = page.locator('#inspiratieSheet .suggestie-card');
|
||||
const cardCount = await cards.count();
|
||||
expect(cardCount).toBe(0);
|
||||
// Empty state text should be visible
|
||||
await expect(page.locator('#tabTussendoortjes .empty-state-text')).toBeVisible();
|
||||
// Empty state text should be visible in the sheet
|
||||
await expect(page.locator('#inspiratieSheet .inspiratie-empty')).toBeVisible();
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: warning label is present', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: warning label is present in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.waitForTimeout(500);
|
||||
const warning = page.locator('.suggestie-warning');
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(600);
|
||||
const warning = page.locator('#inspiratieSheet .suggestie-warning');
|
||||
await expect(warning).toBeVisible();
|
||||
const text = await warning.textContent();
|
||||
expect(text).toContain('NEVO');
|
||||
expect(text).toContain('medisch');
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: each ingredient shows name, grams, and kh', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: each ingredient shows name, grams, and kh in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(2000);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const ingredients = page.locator('.suggestie-ingredient');
|
||||
const ingredients = page.locator('#inspiratieSheet .suggestie-ingredient');
|
||||
const count = await ingredients.count();
|
||||
expect(count).toBeGreaterThanOrEqual(3); // at least 3 total ingredient rows across all cards
|
||||
|
||||
|
|
@ -689,19 +709,21 @@ test.describe('Karby Eetdagboek', () => {
|
|||
expect(khText).toMatch(/\d+[.,]?\d* g kh/); // e.g. "6.7 g kh"
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: range value 0 is not defaulted to 15', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: range value 0 is not defaulted to 15 in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// Set min to 0 — previously this would silently become 15 due to parseInt() || 15
|
||||
await page.locator('#rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMin').fill('0');
|
||||
await page.locator('#inspiratieSheet #rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMin').fill('0');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify the input shows 0
|
||||
await expect(page.locator('#rangeMin')).toHaveValue('0');
|
||||
await expect(page.locator('#inspiratieSheet #rangeMin')).toHaveValue('0');
|
||||
|
||||
// Verify that the app correctly interprets 0 (not falling back to 15)
|
||||
const parseResult = await page.evaluate(() => {
|
||||
|
|
@ -717,17 +739,19 @@ test.describe('Karby Eetdagboek', () => {
|
|||
expect(parseResult.newFallback).toBe(0); // isNaN(0) ? 15 : 0 → 0 (fixed)
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: range 0–1 is interpreted correctly and shows empty state', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: range 0–1 is interpreted correctly and shows empty state in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// Set range to 0–1
|
||||
await page.locator('#rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMax').fill('1');
|
||||
await page.locator('#rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#rangeMin').fill('0');
|
||||
await page.locator('#inspiratieSheet #rangeMax').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMax').fill('1');
|
||||
await page.locator('#inspiratieSheet #rangeMin').click({ clickCount: 3 });
|
||||
await page.locator('#inspiratieSheet #rangeMin').fill('0');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the actual parsed values via berekenReceptTotalen — min should be 0, not 15
|
||||
|
|
@ -743,21 +767,23 @@ test.describe('Karby Eetdagboek', () => {
|
|||
expect(rangeValues.rangeMax).toBe(1);
|
||||
|
||||
// Should show empty state
|
||||
const cards = page.locator('.suggestie-card');
|
||||
const cards = page.locator('#inspiratieSheet .suggestie-card');
|
||||
const cardCount = await cards.count();
|
||||
expect(cardCount).toBe(0);
|
||||
await expect(page.locator('#tabTussendoortjes .empty-state-text')).toBeVisible();
|
||||
await expect(page.locator('#inspiratieSheet .inspiratie-empty')).toBeVisible();
|
||||
});
|
||||
|
||||
test('tussendoortjes tab: range inputs have haptic metadata', async ({ page }) => {
|
||||
test('tussendoortjes inspiratie: range inputs have haptic metadata in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.click('#navDagboek');
|
||||
await page.waitForTimeout(600);
|
||||
await page.locator('.vind-inspiratie-btn').first().click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Both range inputs should have data-haptic attribute
|
||||
await expect(page.locator('#rangeMin')).toHaveAttribute('data-haptic', 'normal');
|
||||
await expect(page.locator('#rangeMax')).toHaveAttribute('data-haptic', 'normal');
|
||||
await expect(page.locator('#inspiratieSheet #rangeMin')).toHaveAttribute('data-haptic', 'normal');
|
||||
await expect(page.locator('#inspiratieSheet #rangeMax')).toHaveAttribute('data-haptic', 'normal');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ const { test, expect } = require('@playwright/test');
|
|||
/**
|
||||
* E2E tests for the "Vind inspiratie" feature on Tussendoor 1/2/3
|
||||
*
|
||||
* The feature opens a bottom sheet (inspiratieSheet) instead of a full tab/page.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - The app must be served at the configured baseURL
|
||||
* - Run `npx playwright test` from the project root
|
||||
|
|
@ -25,13 +27,18 @@ async function goToDagboek(page) {
|
|||
await page.waitForTimeout(400);
|
||||
}
|
||||
|
||||
// Helper: switch to tussendoortjes tab (via switchTab, nav-knop is verwijderd)
|
||||
async function goToTussendoortjes(page) {
|
||||
await page.evaluate(() => switchTab('tussendoortjes'));
|
||||
await page.waitForTimeout(400);
|
||||
// Helper: open inspiratieSheet via Vind inspiratie button on the first Tussendoor card
|
||||
async function openInspiratieSheet(page) {
|
||||
await goToDagboek(page);
|
||||
const inspiratieBtn = page.locator('.vind-inspiratie-btn').first();
|
||||
await expect(inspiratieBtn).toBeVisible();
|
||||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
// Verify sheet is open
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
}
|
||||
|
||||
test.describe('Vind inspiratie feature', () => {
|
||||
test.describe('Vind inspiratie feature (bottom sheet)', () => {
|
||||
|
||||
test('Vind inspiratie button only appears on Tussendoor 1, 2, 3 cards in dagboek', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
|
@ -55,7 +62,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('Clicking Vind inspiratie opens tussendoortjes with correct context title', async ({ page }) => {
|
||||
test('Clicking Vind inspiratie opens bottom sheet with correct context title', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
|
@ -67,16 +74,68 @@ test.describe('Vind inspiratie feature', () => {
|
|||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should now be on tussendoortjes tab
|
||||
const title = page.locator('#tussendoortjesTitle');
|
||||
await expect(title).toBeVisible();
|
||||
// Should now have the inspiratieSheet open (bottom sheet)
|
||||
const sheet = page.locator('#inspiratieSheet');
|
||||
await expect(sheet).toBeVisible();
|
||||
await expect(sheet).not.toHaveClass(/hidden/);
|
||||
|
||||
// Title should start with "Inspiratie voor Tussendoor"
|
||||
// Sheet title should start with "Inspiratie voor Tussendoor"
|
||||
const title = page.locator('#inspiratieSheetTitle');
|
||||
const titleText = await title.textContent();
|
||||
expect(titleText).toContain('Inspiratie voor Tussendoor');
|
||||
});
|
||||
|
||||
test('Vind inspiratie shows context header with date and moment name', async ({ page }) => {
|
||||
test('Afsluiten X-knop sluit bottom sheet en keert terug naar Dagboek', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
||||
// Open inspiratie sheet
|
||||
const inspiratieBtn = page.locator('.vind-inspiratie-btn').first();
|
||||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet should be open
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
|
||||
// Click X close button
|
||||
await page.click('#inspiratieSheetClose');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet should be hidden
|
||||
await expect(page.locator('#inspiratieSheet')).toHaveClass(/hidden/);
|
||||
|
||||
// Dagboek should still be active
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test('Afsluiten via backdrop-click sluit bottom sheet en keert terug naar Dagboek', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
||||
// Open inspiratie sheet
|
||||
const inspiratieBtn = page.locator('.vind-inspiratie-btn').first();
|
||||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet should be open
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
|
||||
// Click the overlay backdrop (not the sheet content)
|
||||
const overlay = page.locator('#inspiratieSheet');
|
||||
// Click outside the modal-sheet area (the overlay itself)
|
||||
await overlay.click({ position: { x: 10, y: 10 } });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet should be hidden
|
||||
await expect(page.locator('#inspiratieSheet')).toHaveClass(/hidden/);
|
||||
|
||||
// Dagboek should still be active
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test('Vind inspiratie shows context header with date and moment name in sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
|
@ -86,7 +145,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Context header should be visible
|
||||
// Context header should be visible in the sheet
|
||||
const contextEl = page.locator('#inspiratieContext');
|
||||
await expect(contextEl).toBeVisible();
|
||||
|
||||
|
|
@ -97,7 +156,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
expect(labelText).toContain('202'); // any year prefix in the date
|
||||
});
|
||||
|
||||
test('Context header close button clears inspiration mode and shows normal title', async ({ page }) => {
|
||||
test('Context header close button clears inspiration mode but keeps sheet open', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
|
@ -110,24 +169,29 @@ test.describe('Vind inspiratie feature', () => {
|
|||
// Context is visible
|
||||
await expect(page.locator('#inspiratieContext')).toBeVisible();
|
||||
|
||||
// Click close
|
||||
// Click context close (× in the context banner, not the sheet X)
|
||||
await page.click('#inspiratieContextClose');
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
// Title should now be "Tussendoortjes"
|
||||
const titleText = await page.locator('#tussendoortjesTitle').textContent();
|
||||
// Title should now show "Tussendoortjes" (inspiratie context cleared)
|
||||
const titleText = await page.locator('#inspiratieSheetTitle').textContent();
|
||||
expect(titleText).toBe('Tussendoortjes');
|
||||
|
||||
// Context should be hidden
|
||||
await expect(page.locator('#inspiratieContext')).toBeHidden();
|
||||
|
||||
// Sheet should still be open (not closed)
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
await expect(page.locator('#inspiratieSheet')).not.toHaveClass(/hidden/);
|
||||
});
|
||||
|
||||
test('Nieuwe suggesties button exists and generates suggestions', async ({ page }) => {
|
||||
test('Nieuwe suggesties button exists in sheet and generates suggestions', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToTussendoortjes(page);
|
||||
// Open sheet via first Vind inspiratie button to get suggestions context
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
// Button should be visible
|
||||
// Button should be visible in the sheet
|
||||
const nieuweBtn = page.locator('#nieuweSuggestiesBtn');
|
||||
await expect(nieuweBtn).toBeVisible();
|
||||
expect(await nieuweBtn.textContent()).toContain('Nieuwe suggesties');
|
||||
|
|
@ -149,7 +213,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
test('Nieuwe suggesties changes the set on consecutive presses', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToTussendoortjes(page);
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
const nieuweBtn = page.locator('#nieuweSuggestiesBtn');
|
||||
|
||||
|
|
@ -181,7 +245,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
test('All shown suggestion totals are within the carb range (default 15-20g)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToTussendoortjes(page);
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
// Click Nieuwe suggesties a few times to increase coverage
|
||||
const nieuweBtn = page.locator('#nieuweSuggestiesBtn');
|
||||
|
|
@ -216,11 +280,9 @@ test.describe('Vind inspiratie feature', () => {
|
|||
expect(haptic).toBeTruthy();
|
||||
}
|
||||
|
||||
await goToTussendoortjes(page);
|
||||
// Open sheet and check Nieuwe suggesties button exists inside
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
// Check Nieuwe suggesties button has haptic
|
||||
// (data-haptic is set globally via the event delegation on buttons,
|
||||
// but the button itself should trigger haptic via the global setup)
|
||||
const nieuweBtn = page.locator('#nieuweSuggestiesBtn');
|
||||
await expect(nieuweBtn).toBeVisible();
|
||||
});
|
||||
|
|
@ -228,7 +290,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
test('All shown ingredients have valid NEVO-n in database; kaneel uses actual DB value (56g/100g, not 0)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToTussendoortjes(page);
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
const nieuweBtn = page.locator('#nieuweSuggestiesBtn');
|
||||
|
||||
|
|
@ -303,7 +365,7 @@ test.describe('Vind inspiratie feature', () => {
|
|||
test('Generated recipe titles use friendly names, not raw NEVO display names', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToTussendoortjes(page);
|
||||
await openInspiratieSheet(page);
|
||||
|
||||
// Known NEVO display-name fragments that should NEVER appear in a recipe title.
|
||||
// These are raw NEVO aliases that get stripped by displayNaam() — NOT the friendly
|
||||
|
|
@ -345,4 +407,63 @@ test.describe('Vind inspiratie feature', () => {
|
|||
console.log(`OK: Checked ${titlesChecked} generated titles — no NEVO alias fragments found`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Dagboek blijft actief na openen en sluiten van inspiratie-sheet', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
||||
// Verify Dagboek is active initially
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
|
||||
// Open sheet
|
||||
const inspiratieBtn = page.locator('.vind-inspiratie-btn').first();
|
||||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet is open
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
|
||||
// Dagboek should still be active (no tab switch)
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
|
||||
// Close sheet via X
|
||||
await page.click('#inspiratieSheetClose');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet is hidden
|
||||
await expect(page.locator('#inspiratieSheet')).toHaveClass(/hidden/);
|
||||
|
||||
// Dagboek should still be active
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test('Geen navigatie naar tussendoortjes tab (tabTussendoortjes blijft leeg/verborgen)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForApp(page);
|
||||
await goToDagboek(page);
|
||||
|
||||
// Open inspiratie sheet
|
||||
const inspiratieBtn = page.locator('.vind-inspiratie-btn').first();
|
||||
await inspiratieBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Sheet is visible
|
||||
await expect(page.locator('#inspiratieSheet')).toBeVisible();
|
||||
|
||||
// tabTussendoortjes should either be hidden or empty
|
||||
const tussendoortjesTab = page.locator('#tabTussendoortjes');
|
||||
const classAttr = await tussendoortjesTab.getAttribute('class');
|
||||
const isHidden = classAttr && classAttr.includes('hidden');
|
||||
|
||||
if (!isHidden) {
|
||||
// If visible, it should be empty (no suggestieList in it, no content)
|
||||
const suggestieCount = await tussendoortjesTab.locator('.suggestie-card').count();
|
||||
expect(suggestieCount).toBe(0);
|
||||
}
|
||||
|
||||
// Dagboek stays active
|
||||
await expect(page.locator('#navDagboek')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue