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, '..'); function createHarness() { const document = {}; const elements = new Map(); function makeElement(id) { if (elements.has(id)) return elements.get(id); const element = { id, attrs: {}, css: {}, hidden: false, textValue: '', toggled: null, paused: false, loadCount: 0, pause() { this.paused = true; }, load() { this.loadCount += 1; }, play() { return Promise.resolve(); }, removeAttribute(name) { delete this.attrs[name]; if (name === 'src') delete this.src; }, getAttribute(name) { if (name === 'src') return this.src ?? this.attrs.src ?? null; return this.attrs[name] ?? null; } }; elements.set(id, element); return element; } class Wrapper { constructor(target) { this.target = target; this[0] = target; this.length = target ? 1 : 0; } on() { return this; } off() { return this; } button() { return this; } hide() { if (this.target) this.target.hidden = true; return this; } removeAttr(name) { this.target?.removeAttribute?.(name); if (this.target && name === 'hidden') this.target.hidden = false; return this; } attr(name, value) { if (!this.target) return this; if (value === undefined) return this.target.attrs?.[name]; this.target.attrs[name] = String(value); if (name === 'src') this.target.src = String(value); if (name === 'hidden') this.target.hidden = true; return this; } css(name, value) { if (this.target) this.target.css[name] = value; return this; } toggle(value) { if (this.target) this.target.toggled = Boolean(value); return this; } text(value) { if (this.target) this.target.textValue = String(value); return this; } prop(name, value) { if (this.target) this.target[name] = value; return this; } hasClass() { return false; } closest() { return { length: 0 }; } } function jquery(target) { if (target === document) return new Wrapper(document); if (typeof target === 'string' && target.startsWith('#')) return new Wrapper(makeElement(target.slice(1))); return new Wrapper(target); } const context = { Date, Math, Promise, document, globalThis: null, jQuery: jquery, Wegwichtel: {} }; context.globalThis = context; return { context, elements }; } async function loadModule(relativePath, harness) { const sourcePath = path.join(root, relativePath); const source = await fs.readFile(sourcePath, 'utf8'); vm.runInNewContext(source, harness.context, { filename: sourcePath }); } test('audio player preserves local data/blob URLs and cache-busts server URLs', async () => { const harness = createHarness(); await loadModule('public/js/audio-player.js', harness); const dataUrl = 'data:audio/mpeg;base64,QUJDRA=='; harness.context.Wegwichtel.AudioPlayer.load(dataUrl); assert.equal(harness.elements.get('poi-audio').src, dataUrl); const blobUrl = 'blob:https://wegwichtel.de/1234'; harness.context.Wegwichtel.AudioPlayer.load(blobUrl); assert.equal(harness.elements.get('poi-audio').src, blobUrl); harness.context.Wegwichtel.AudioPlayer.load('/api/routes/1/pois/2/audio'); assert.match(harness.elements.get('poi-audio').src, /^\/api\/routes\/1\/pois\/2\/audio\?v=\d+$/); }); test('slideshow preserves local data/blob URLs and cache-busts server URLs', async () => { const harness = createHarness(); await loadModule('public/js/slideshow.js', harness); const dataUrl = 'data:image/jpeg;base64,QUJDRA=='; harness.context.Wegwichtel.Slideshow.show([{ url: dataUrl, caption: 'Lokal' }]); assert.equal(harness.elements.get('slide-image').src, dataUrl); assert.equal(harness.elements.get('slideshow').toggled, true); const blobUrl = 'blob:https://wegwichtel.de/5678'; harness.context.Wegwichtel.Slideshow.show([{ url: blobUrl }]); assert.equal(harness.elements.get('slide-image').src, blobUrl); harness.context.Wegwichtel.Slideshow.show([{ url: '/api/routes/1/pois/2/pictures/3' }]); assert.match(harness.elements.get('slide-image').src, /^\/api\/routes\/1\/pois\/2\/pictures\/3\?v=\d+$/); });