import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import vm from 'node:vm'; const root = path.resolve(import.meta.dirname, '..'); async function read(relativePath) { return fs.readFile(path.join(root, relativePath), 'utf8'); } test('start screen exposes cookie status, optional local progress and deliberate entry', async () => { const html = await read('public/index.html'); const loader = await read('public/js/bootstrap-loader.js'); const storage = await read('public/js/privacy-storage.js'); assert.match(html, /Diese Anwendung setzt derzeit keine Cookies/); assert.match(html, /class="cookie-toggle"[^>]*disabled/); assert.match(html, /class="progress-storage-toggle"/); assert.match(html, /id="storage-cleanup-status"/); assert.match(html, /id="bootstrap-enter"[^>]*hidden/); assert.match(loader, /Entferne alte Streckendaten aus dem Speicher \(\$\{removed\} \/ \$\{total\}\)/); assert.match(loader, /showReadyState\(\)/); assert.doesNotMatch(loader, /getElementById\('bootstrap-screen'\)\.hidden = true/); assert.match(loader, /event\.target\.closest\('button, input, label, a, dialog'\)/); assert.match(storage, /MAX_PROGRESS_AGE_MS = 24 \* 60 \* 60 \* 1000/); assert.match(storage, /cookiesEnabled: \(\) => false/); }); test('information button opens scrollable overlay pages for privacy, imprint and technology', async () => { const html = await read('public/index.html'); const css = await read('public/css/app.css'); const ui = await read('public/js/legal-ui.js'); assert.match(html, /id="info-button"/); assert.match(html, / { const app = await read('public/js/app.js'); assert.match(app, /PrivacyStorage\.saveRouteState/); assert.match(app, /PrivacyStorage\.activateRoute/); assert.match(app, /state\.viewed\.add\(state\.activePoiId\)/); assert.match(app, /\$\('#poi-audio'\)\.on\('play'/); assert.match(app, /state\.playedAudio\.add\(state\.activePoiId\)/); assert.match(app, /globalThis\.addEventListener\('pagehide', persistRouteProgress\)/); assert.match(app, /await initializeLocation\(context\)/); }); class FakeElement extends EventTarget { constructor(id = '') { super(); this.id = id; this.hidden = false; this.checked = false; this.disabled = false; this.open = false; this.textContent = ''; this.scrollTop = 0; this.dataset = {}; this.attributes = new Map(); this.focused = false; } focus() { this.focused = true; } setAttribute(name, value) { this.attributes.set(name, String(value)); if (name === 'open') this.open = true; } removeAttribute(name) { this.attributes.delete(name); if (name === 'open') this.open = false; } showModal() { this.open = true; } close() { this.open = false; } } function pointerEvent() { const event = new Event('pointerup', { bubbles: true, cancelable: true }); Object.defineProperties(event, { isPrimary: { value: true }, pointerType: { value: 'touch' }, button: { value: 0 } }); return event; } test('legal overlay handlers synchronize switches without advancing the start screen', async () => { const sourcePath = path.join(root, 'public/js/legal-ui.js'); const source = await fs.readFile(sourcePath, 'utf8'); const elements = new Map([ ['info-dialog', new FakeElement('info-dialog')], ['info-dialog-title', new FakeElement('info-dialog-title')], ['info-menu', new FakeElement('info-menu')], ['info-back', new FakeElement('info-back')], ['info-close', new FakeElement('info-close')], ['info-button', new FakeElement('info-button')] ]); const content = new FakeElement(); const privacyPage = new FakeElement(); const imprintPage = new FakeElement(); const technologyPage = new FakeElement(); const pages = { privacy: privacyPage, imprint: imprintPage, technology: technologyPage }; const targetButtons = Object.keys(pages).map(name => { const button = new FakeElement(); button.dataset.infoTarget = name; return button; }); const progressToggles = [new FakeElement(), new FakeElement()]; const cookieToggles = [new FakeElement()]; let enabled = false; const changes = []; class TestCustomEvent extends Event { constructor(type, options = {}) { super(type); this.detail = options.detail; } } const document = new EventTarget(); document.getElementById = id => elements.get(id); document.querySelectorAll = selector => { if (selector === '.progress-storage-toggle') return progressToggles; if (selector === '.cookie-toggle') return cookieToggles; if (selector === '[data-info-page]') return Object.values(pages); if (selector === '[data-info-target]') return targetButtons; return []; }; document.querySelector = selector => { if (selector === '.info-dialog-content') return content; const match = selector.match(/^\[data-info-page="(.+)"\]$/); return match ? pages[match[1]] : null; }; document.addEventListener('wegwichtel:progress-storage-changed', event => changes.push(event.detail.enabled)); const context = { Wegwichtel: { PrivacyStorage: { isProgressStorageEnabled: () => enabled, setProgressStorageEnabled: value => { enabled = value; return enabled; } } }, document, Event, EventTarget, CustomEvent: TestCustomEvent }; context.globalThis = context; vm.runInNewContext(source, context, { filename: sourcePath }); elements.get('info-button').dispatchEvent(pointerEvent()); assert.equal(elements.get('info-dialog').open, true); assert.equal(elements.get('info-menu').hidden, false); targetButtons[0].dispatchEvent(pointerEvent()); assert.equal(privacyPage.hidden, false); assert.equal(elements.get('info-back').hidden, false); assert.equal(elements.get('info-dialog-title').textContent, 'Datenschutzerklärung'); progressToggles[0].checked = true; progressToggles[0].dispatchEvent(new Event('change')); assert.deepEqual(changes, [true]); assert.equal(progressToggles[1].checked, true); assert.equal(cookieToggles[0].checked, false); elements.get('info-back').dispatchEvent(pointerEvent()); assert.equal(elements.get('info-menu').hidden, false); elements.get('info-close').dispatchEvent(pointerEvent()); assert.equal(elements.get('info-dialog').open, false); });