process.env.TZ = 'Europe/Berlin'; const { test } = require('node:test'); const assert = require('node:assert/strict'); const { readFileSync } = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const { parse, compileScript } = require('@vue/compiler-sfc'); const { transformSync } = require('esbuild'); const vue = require('vue'); // Execute the real Vue setup and store code, with HTTP responses controlled by // each test. No mounted app or browser globals are needed for these load paths. function load(relativePath, dependencies = {}) { const filename = path.resolve(__dirname, '..', relativePath); let source = readFileSync(filename, 'utf8'); if (filename.endsWith('.vue')) { source = compileScript(parse(source).descriptor, { id: 'load-error-test' }).content; } const code = transformSync(source, { format: 'cjs', loader: 'js' }).code; const module = { exports: {} }; vm.runInThisContext(`(function(require, module, exports) { ${code}\n})`, { filename })(id => { if (id in dependencies) return dependencies[id]; if (id === 'vue') return { ...vue, onMounted() {}, onBeforeUnmount() {} }; if (id === 'pinia') return { defineStore: (_name, setup) => setup }; if (id === '@/config/api') return { API_BASE_URL: '/api' }; if (id.endsWith('authStore')) return { useAuthStore: () => ({ token: 'test-token', getAuthHeaders: () => ({}), clearAuth() {} }) }; throw new Error(`Unexpected import: ${id}`); }, module, module.exports); return module.exports; } function component(file, dependencies) { return load(file, dependencies).default.setup({}, { expose() {} }); } const ok = data => ({ ok: true, json: async () => data }); test('Wochenansicht fordert beim Öffnen aktuelle Woche an', async t => { let requestedUrl; t.mock.method(global, 'fetch', async url => { requestedUrl = url; return ok({ success: true, data: { weekStart: '2026-10-26', weekEnd: '2026-11-01', days: [] } }); }); const page = component('src/views/WeekOverview.vue'); await page.loadWeekData(); assert.equal(requestedUrl, '/api/week-overview?weekOffset=0'); assert.equal(page.weekRange.value, '26.10.2026 - 01.11.2026'); assert.equal(page.loadError.value, ''); }); test('Wochenansicht zeigt bei HTTP 500 Fehler und aktuelle Wochengrenzen statt Beispieldaten', async t => { const RealDate = Date; t.mock.method(global, 'Date', class extends RealDate { constructor(...args) { super(...(args.length ? args : ['2026-10-27T14:00:00'])); } }); t.mock.method(console, 'error', () => {}); t.mock.method(global, 'fetch', async () => ({ ok: false, status: 500 })); const page = component('src/views/WeekOverview.vue'); await page.loadWeekData(); assert.equal(page.weekData.value, null); assert.deepEqual(page.weekDays.value, []); assert.equal(page.weekRange.value, '26.10.2026 - 01.11.2026'); assert.match(page.loadError.value, /nicht geladen/); assert.equal(page.loading.value, false); }); test('Ältere Wochenantwort überschreibt die zuletzt ausgewählte Woche nicht', async t => { let finishOldRequest; t.mock.method(global, 'fetch', url => url.endsWith('=0') ? new Promise(resolve => { finishOldRequest = resolve; }) : Promise.resolve(ok({ success: true, data: { weekStart: '2026-11-02', weekEnd: '2026-11-08', days: [] } }))); const page = component('src/views/WeekOverview.vue'); const oldRequest = page.loadWeekData(); page.weekOffset.value = 1; await page.loadWeekData(); finishOldRequest(ok({ success: true, data: { weekStart: '2026-10-26', weekEnd: '2026-11-01', days: [] } })); await oldRequest; assert.equal(page.weekRange.value, '02.11.2026 - 08.11.2026'); }); test('Statistikfehler wird angezeigt, gültige Werte bleiben erhalten und Wiederholen funktioniert', async t => { t.mock.method(console, 'error', () => {}); const stats = { timestamp: new Date().toISOString(), currentlyWorked: '03:53:53', open: '03:06:07', overtime: '+0:00', totalOvertime: '+5:49', weekWorktime: '3:53', nonWorkingHours: '8:00' }; let failing = true; t.mock.method(global, 'fetch', async () => failing ? { ok: false, status: 500 } : ok(stats)); const store = load('src/stores/timeStore.js'); const box = component('src/components/StatusBox.vue', { '../stores/timeStore': store }); await box.fetchStats(); assert.match(box.statsError.value, /nicht geladen/); assert.equal(box.isReady.value, true); assert.equal(box.displayRows.value['Überstunden (Woche)'], '—'); assert.equal('overtime' in box.displayRows.value, false); assert.equal('totalOvertime' in box.displayRows.value, false); failing = false; await box.fetchStats(); assert.equal(box.statsError.value, ''); assert.equal(box.displayRows.value['Überstunden (Gesamt)'], '+5:49 h'); failing = true; await box.fetchStats(); assert.match(box.statsError.value, /veraltet/); assert.equal(box.displayRows.value['Überstunden (Gesamt)'], '+5:49 h'); });