Compare commits

...

6 Commits

Author SHA1 Message Date
Kantine Wrapper
98020f0b8f chore: update build artifacts for v1.4.17 2026-02-24 20:40:55 +01:00
Kantine Wrapper
c2e3282131 fix(ui): restore highlight modal toggle event & add dom test suite (v1.4.17) 2026-02-24 20:40:54 +01:00
Kantine Wrapper
7a82cb06db chore: update build artifacts for v1.4.16 2026-02-24 15:47:13 +01:00
Kantine Wrapper
d89b080da5 feat: add clear local cache button to version menu (v1.4.16) 2026-02-24 15:47:13 +01:00
Kantine Wrapper
d80863a169 chore: update build artifacts for v1.4.15 2026-02-24 15:38:44 +01:00
Kantine Wrapper
ae79c58d30 fix(flags): properly remove expired flags from localstorage (v1.4.15) 2026-02-24 15:38:44 +01:00
12 changed files with 241 additions and 41 deletions

View File

@@ -48,6 +48,11 @@ trigger: always_on
- **Browser**: Allowed for documentation and safe browsing. No automated logins without permission. - **Browser**: Allowed for documentation and safe browsing. No automated logins without permission.
- **Terminal**: No `rm -rf`. Run tests (`pytest` etc.) after logic changes. - **Terminal**: No `rm -rf`. Run tests (`pytest` etc.) after logic changes.
## 7. Mandatory Testing Policy 🧪
**CRITICAL: No logic or UI fix is complete without a corresponding automated test.**
- If you fix a regression or implement a new UI feature, you **MUST** write or update a test in `tests/test_dom.js` or `test_logic.js`.
- Refactoring MUST include verifying that no click listeners drop out. This guarantees that features like modal toggles stay functional.
## 7. Requirements-Konsistenz 📋 ## 7. Requirements-Konsistenz 📋
Alle umgesetzten Anforderungen müssen mit `REQUIREMENTS.md` übereinstimmen. Alle umgesetzten Anforderungen müssen mit `REQUIREMENTS.md` übereinstimmen.
1. **Vor der Umsetzung prüfen**: Passt die neue Anforderung zu den bestehenden Requirements? 1. **Vor der Umsetzung prüfen**: Passt die neue Anforderung zu den bestehenden Requirements?

View File

@@ -258,6 +258,14 @@ if [ $LOGIC_EXIT -ne 0 ]; then
exit 1 exit 1
fi fi
echo "=== Running DOM Interaction Tests ==="
node "$SCRIPT_DIR/tests/test_dom.js"
DOM_EXIT=$?
if [ $DOM_EXIT -ne 0 ]; then
echo "❌ DOM UI tests FAILED! Regressions detected."
exit 1
fi
echo "=== Running Build Tests ===" echo "=== Running Build Tests ==="
python3 "$SCRIPT_DIR/test_build.py" python3 "$SCRIPT_DIR/test_build.py"
TEST_EXIT=$? TEST_EXIT=$?

View File

@@ -1,3 +1,13 @@
## v1.4.17
- 🐛 **Bugfix**: Regression behoben: Der "Persönliche Highlights" (Stern-Button) Dialog öffnet sich nun wieder korrekt.
- 🧪 **Testing**: Es wurde ein initialer UI-Testing-Hook (`test_dom.js` mit `jsdom`) in die Build-Pipeline integriert, um kritische DOM Event-Listener Regressionen (wie den Highlights-Button und die Alarmglocke) automatisch zu preventen.
## v1.4.16
-**Feature**: Ein Button "Lokalen Cache leeren" wurde zum Versionen-Menü hinzugefügt, um bei hartnäckigen lokalen Fehlern alle Caches und Sessions bereinigen zu können, ohne die Entwicklertools (F12) des Browsers bemühen zu müssen.
## v1.4.15
- 🧹 **Bugfix**: In der Vergangenheit gesetzte Alarme/Flags wurden nicht zuverlässig gelöscht. Dies ist nun behoben, sodass verfallene Menüs nach 10:00 Uhr bzw. an vergangenen Tagen automatisch aus dem Tracker verschwinden.
## v1.4.14 ## v1.4.14
- 🐛 **Bugfix**: Alarmglocke versteckt sich jetzt zuverlässig auch auf Endgeräten mit CSS Konflikten - 🐛 **Bugfix**: Alarmglocke versteckt sich jetzt zuverlässig auch auf Endgeräten mit CSS Konflikten
- 🚀 **Feature**: Sofortige API-Aktualisierung (Refresh) bei Aktivierung eines Menüalarms - 🚀 **Feature**: Sofortige API-Aktualisierung (Refresh) bei Aktivierung eines Menüalarms

15
debug_test.js Executable file
View File

@@ -0,0 +1,15 @@
const fs = require('fs');
const jsCode = fs.readFileSync('kantine.js', 'utf8').replace('(function () {', '').replace(/}\)\(\);$/, '');
try {
const vm = require('vm');
new vm.Script(jsCode);
} catch (e) {
console.error(e.message);
const lines = jsCode.split('\n');
console.error("Around line", e.loc?.line);
if(e.loc?.line) {
console.log(lines[e.loc.line - 2]);
console.log(lines[e.loc.line - 1]);
console.log(lines[e.loc.line]);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

25
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -2021,7 +2021,7 @@ body {
<div class="brand"> <div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span> <span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="header-left"> <div class="header-left">
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.14</small></h1> <h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.17</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div> <div id="last-updated-subtitle" class="subtitle"></div>
</div> </div>
<div class="nav-group" style="margin-left: 1rem;"> <div class="nav-group" style="margin-left: 1rem;">
@@ -2163,7 +2163,7 @@ body {
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<strong>Aktuell:</strong> <span id="version-current">v1.4.14</span> <strong>Aktuell:</strong> <span id="version-current">v1.4.17</span>
</div> </div>
<div class="dev-toggle"> <div class="dev-toggle">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;"> <label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
@@ -2181,6 +2181,9 @@ body {
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor"> <a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen <span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
</a> </a>
<button id="btn-clear-cache" style="background: none; border: none; padding: 0; color: var(--error-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem; cursor: pointer; text-align: left; font-size: inherit; font-family: inherit;" title="Löscht alle lokalen Daten & erzwingt einen Neuladen">
<span class="material-icons-round" style="font-size: 1.2em;">delete_forever</span> Lokalen Cache leeren
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -2228,6 +2231,18 @@ body {
const historyModal = document.getElementById('history-modal'); const historyModal = document.getElementById('history-modal');
const btnHistoryClose = document.getElementById('btn-history-close'); const btnHistoryClose = document.getElementById('btn-history-close');
if (btnHighlights) {
btnHighlights.addEventListener('click', () => {
highlightsModal.classList.remove('hidden');
});
}
if (btnHighlightsClose) {
btnHighlightsClose.addEventListener('click', () => {
highlightsModal.classList.add('hidden');
});
}
btnHistory.addEventListener('click', () => { btnHistory.addEventListener('click', () => {
if (!authToken) { if (!authToken) {
loginModal.classList.remove('hidden'); loginModal.classList.remove('hidden');
@@ -2265,6 +2280,17 @@ body {
}); });
} }
const btnClearCache = document.getElementById('btn-clear-cache');
if (btnClearCache) {
btnClearCache.addEventListener('click', () => {
if (confirm('Möchtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) löschen? Die Seite wird danach neu geladen.')) {
localStorage.clear();
sessionStorage.clear();
window.location.reload();
}
});
}
window.addEventListener('click', (e) => { window.addEventListener('click', (e) => {
if (e.target === versionModal) versionModal.classList.add('hidden'); if (e.target === versionModal) versionModal.classList.add('hidden');
}); });
@@ -3034,12 +3060,27 @@ body {
// FR-019: Auto-remove flags whose cutoff has passed // FR-019: Auto-remove flags whose cutoff has passed
function cleanupExpiredFlags() { function cleanupExpiredFlags() {
const now = new Date(); const now = new Date();
const todayStr = now.toISOString().split('T')[0]; // Format: YYYY-MM-DD
let changed = false; let changed = false;
for (const flagId of [...userFlags]) { for (const flagId of [...userFlags]) {
const [date] = flagId.split('_'); const [dateStr] = flagId.split('_'); // Format usually is YYYY-MM-DD
const cutoff = new Date(date);
// If the flag's date string is entirely in the past (before today)
// or if it's today but past the 10:00 cutoff time
let isExpired = false;
if (dateStr < todayStr) {
isExpired = true;
} else if (dateStr === todayStr) {
const cutoff = new Date(dateStr);
cutoff.setHours(10, 0, 0, 0); // Standard cutoff 10:00 cutoff.setHours(10, 0, 0, 0); // Standard cutoff 10:00
if (now >= cutoff) { if (now >= cutoff) {
isExpired = true;
}
}
if (isExpired) {
userFlags.delete(flagId); userFlags.delete(flagId);
changed = true; changed = true;
} }
@@ -3969,7 +4010,7 @@ body {
// Periodic update check (runs on init + every hour) // Periodic update check (runs on init + every hour)
async function checkForUpdates() { async function checkForUpdates() {
const currentVersion = 'v1.4.14'; const currentVersion = 'v1.4.17';
const devMode = localStorage.getItem('kantine_dev_mode') === 'true'; const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
try { try {
@@ -4010,7 +4051,7 @@ body {
const modal = document.getElementById('version-modal'); const modal = document.getElementById('version-modal');
const container = document.getElementById('version-list-container'); const container = document.getElementById('version-list-container');
const devToggle = document.getElementById('dev-mode-toggle'); const devToggle = document.getElementById('dev-mode-toggle');
const currentVersion = 'v1.4.14'; const currentVersion = 'v1.4.17';
if (!modal) return; if (!modal) return;
modal.classList.remove('hidden'); modal.classList.remove('hidden');

View File

@@ -231,6 +231,9 @@
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor"> <a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen <span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
</a> </a>
<button id="btn-clear-cache" style="background: none; border: none; padding: 0; color: var(--error-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem; cursor: pointer; text-align: left; font-size: inherit; font-family: inherit;" title="Löscht alle lokalen Daten & erzwingt einen Neuladen">
<span class="material-icons-round" style="font-size: 1.2em;">delete_forever</span> Lokalen Cache leeren
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -278,6 +281,18 @@
const historyModal = document.getElementById('history-modal'); const historyModal = document.getElementById('history-modal');
const btnHistoryClose = document.getElementById('btn-history-close'); const btnHistoryClose = document.getElementById('btn-history-close');
if (btnHighlights) {
btnHighlights.addEventListener('click', () => {
highlightsModal.classList.remove('hidden');
});
}
if (btnHighlightsClose) {
btnHighlightsClose.addEventListener('click', () => {
highlightsModal.classList.add('hidden');
});
}
btnHistory.addEventListener('click', () => { btnHistory.addEventListener('click', () => {
if (!authToken) { if (!authToken) {
loginModal.classList.remove('hidden'); loginModal.classList.remove('hidden');
@@ -315,6 +330,17 @@
}); });
} }
const btnClearCache = document.getElementById('btn-clear-cache');
if (btnClearCache) {
btnClearCache.addEventListener('click', () => {
if (confirm('Möchtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) löschen? Die Seite wird danach neu geladen.')) {
localStorage.clear();
sessionStorage.clear();
window.location.reload();
}
});
}
window.addEventListener('click', (e) => { window.addEventListener('click', (e) => {
if (e.target === versionModal) versionModal.classList.add('hidden'); if (e.target === versionModal) versionModal.classList.add('hidden');
}); });
@@ -1084,12 +1110,27 @@
// FR-019: Auto-remove flags whose cutoff has passed // FR-019: Auto-remove flags whose cutoff has passed
function cleanupExpiredFlags() { function cleanupExpiredFlags() {
const now = new Date(); const now = new Date();
const todayStr = now.toISOString().split('T')[0]; // Format: YYYY-MM-DD
let changed = false; let changed = false;
for (const flagId of [...userFlags]) { for (const flagId of [...userFlags]) {
const [date] = flagId.split('_'); const [dateStr] = flagId.split('_'); // Format usually is YYYY-MM-DD
const cutoff = new Date(date);
// If the flag's date string is entirely in the past (before today)
// or if it's today but past the 10:00 cutoff time
let isExpired = false;
if (dateStr < todayStr) {
isExpired = true;
} else if (dateStr === todayStr) {
const cutoff = new Date(dateStr);
cutoff.setHours(10, 0, 0, 0); // Standard cutoff 10:00 cutoff.setHours(10, 0, 0, 0); // Standard cutoff 10:00
if (now >= cutoff) { if (now >= cutoff) {
isExpired = true;
}
}
if (isExpired) {
userFlags.delete(flagId); userFlags.delete(flagId);
changed = true; changed = true;
} }

17
syntax_check.js Executable file
View File

@@ -0,0 +1,17 @@
const fs = require('fs');
const jsCode = fs.readFileSync('kantine.js', 'utf8')
.replace('(function () {', '')
.replace('})();', '')
.replace('if (window.__KANTINE_LOADED) return;', '');
const testCode = `
console.log("TEST");
`;
const code = jsCode + '\n' + testCode;
try {
const vm = require('vm');
new vm.Script(code);
} catch (e) {
if(e.stack) {
console.log("Syntax error at:", e.stack.split('\n').slice(0,3).join('\n'));
}
}

View File

@@ -1,7 +1,12 @@
const fs = require('fs'); const fs = require('fs');
fs.writeFileSync('trace.log', '');
function log(m) { fs.appendFileSync('trace.log', m + '\n'); }
log("Initializing JSDOM...");
const jsdom = require('jsdom'); const jsdom = require('jsdom');
const { JSDOM } = jsdom; const { JSDOM } = jsdom;
log("Reading html...");
const html = ` const html = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -15,35 +20,80 @@ const html = `
<button id="alarm-bell" class="icon-btn hidden"> <button id="alarm-bell" class="icon-btn hidden">
<span id="alarm-bell-icon" style="color:var(--text-secondary);"></span> <span id="alarm-bell-icon" style="color:var(--text-secondary);"></span>
</button> </button>
<!-- Mocks for Highlights Feature -->
<button id="btn-highlights">Highlights</button>
<div id="highlights-modal" class="modal hidden">
<button id="btn-highlights-close">Close</button>
<input id="tag-input" type="text" />
<button id="btn-add-tag">Add</button>
<ul id="tags-list"></ul>
</div>
</body> </body>
</html> </html>
`; `;
const jsCode = fs.readFileSync('kantine.js', 'utf8').replace('(function () {', '').replace(/}\)\(\);$/, ''); log("Reading file jsCode...");
const jsCode = fs.readFileSync('kantine.js', 'utf8')
.replace('(function () {', '')
.replace('})();', '')
.replace('if (window.__KANTINE_LOADED) return;', '');
const dom = new JSDOM(html, { runScripts: "dangerously" }); log("Instantiating JSDOM...");
const dom = new JSDOM(html, { runScripts: "dangerously", url: "http://localhost/" });
log("JSDOM dom created...");
global.window = dom.window; global.window = dom.window;
global.document = window.document; global.document = window.document;
global.localStorage = { getItem: () => '[]', setItem: () => {} }; global.localStorage = { getItem: () => '[]', setItem: () => { } };
global.sessionStorage = { getItem: () => null }; global.sessionStorage = { getItem: () => null };
global.showToast = () => {}; global.showToast = () => { };
global.saveFlags = () => {}; global.saveFlags = () => { };
global.renderVisibleWeeks = () => {}; global.renderVisibleWeeks = () => { };
// Mock missing browser features if needed // Mock missing browser features if needed
global.Notification = { permission: 'default', requestPermission: () => {} }; global.Notification = { permission: 'default', requestPermission: () => { } };
global.window.matchMedia = () => ({ matches: false, addListener: () => { }, removeListener: () => { } });
try { global.fetch = () => Promise.resolve({ ok: true, json: () => Promise.resolve({ results: [] }) });
dom.window.eval(jsCode); global.window.fetch = global.fetch;
console.log("Initial Bell Classes:", window.document.getElementById('alarm-bell').className);
log("Before eval...");
const testCode = `
console.log("--- Testing Alarm Bell ---");
// Add flag // Add flag
dom.window.eval("userFlags.add('2026-02-24_123'); updateAlarmBell();"); userFlags.add('2026-02-24_123'); updateAlarmBell();
console.log("After Add:", window.document.getElementById('alarm-bell').className); if (document.getElementById('alarm-bell').className.includes('hidden')) throw new Error("Bell should be visible");
// Remove flag // Remove flag
dom.window.eval("userFlags.delete('2026-02-24_123'); updateAlarmBell();"); userFlags.delete('2026-02-24_123'); updateAlarmBell();
console.log("After Remove:", window.document.getElementById('alarm-bell').className); if (!document.getElementById('alarm-bell').className.includes('hidden')) throw new Error("Bell should be hidden");
} catch (e) {
console.error(e); console.log("✅ Alarm Bell Test Passed");
console.log("--- Testing Highlights Modal ---");
// First, verify initial state
const hlModal = document.getElementById('highlights-modal');
if (!hlModal.classList.contains('hidden')) throw new Error("Highlights modal should be hidden initially");
// Call bindEvents manually to attach the listeners since the IIFE is stripped
bindEvents();
// Click to open
document.getElementById('btn-highlights').click();
if (hlModal.classList.contains('hidden')) throw new Error("Highlights modal did not open upon clicking btn-highlights!");
// Click to close
document.getElementById('btn-highlights-close').click();
if (!hlModal.classList.contains('hidden')) throw new Error("Highlights modal did not close upon clicking btn-highlights-close!");
console.log("✅ Highlights Modal Test Passed");
window.__TEST_PASSED = true;
`;
dom.window.eval(jsCode + "\n" + testCode);
if (!dom.window.__TEST_PASSED) {
throw new Error("Tests failed to reach completion inside JSDOM.");
} }
process.exit(0);

View File

@@ -1 +1 @@
v1.4.14 v1.4.17