Wegwichtel/test/legal-ui.test.js

199 lines
7.0 KiB
JavaScript

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, /<dialog id="info-dialog"/);
assert.match(html, /data-info-target="privacy"/);
assert.match(html, /data-info-target="imprint"/);
assert.match(html, /data-info-target="technology"/);
assert.match(html, /wegwichtel\.privacy\.v1/);
assert.match(html, /wegwichtel\.route-progress\.v1/);
assert.match(html, /jQuery 4\.0\.0/);
assert.match(css, /\.info-dialog-content[\s\S]*overflow-y:\s*auto/);
assert.match(css, /\.info-dialog-content[\s\S]*touch-action:\s*pan-y/);
assert.match(ui, /pointerup/);
assert.match(ui, /keydown/);
assert.doesNotMatch(ui, /addEventListener\(['"]click['"]/);
});
test('application persists current route, viewed stations and played audio only through privacy storage', async () => {
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);
});