Files
Wegwichtel/test/route-recorder.test.js
T
Florian Zumpe 0db91660b3
Sonarqube Scanner / Prepare environment (push) Successful in 8s
Sonarqube Scanner / Build and test (push) Successful in 21s
Sonarqube Scanner / SonarQube analysis (push) Failing after 1m3s
Sonarqube Scanner / Deploy to Server (push) Successful in 6s
allow update for uploaded routes and implement creator id for routes
2026-10-03 16:43:08 +02:00

129 lines
6.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import vm from 'node:vm';
import { loadI18n } from '../test-support/i18n.js';
const sourcePath = new URL('../public/js/route-recorder.js', import.meta.url);
const tick = () => new Promise(resolve => setImmediate(resolve));
async function setup() {
class Element extends EventTarget {
constructor() { super(); this.children = []; this.value = ''; this.attributes = {}; }
setAttribute(key, value) { this.attributes[key] = value; }
appendChild(child) { this.children.push(child); }
replaceChildren() { this.children = []; }
}
const ids = ['route-recording-control', 'route-recording-status', 'route-recording-pause', 'route-recording-stop', 'editor-recorded-positions', 'editor-add-position'];
const nodes = new Map(ids.map(id => [id, new Element()]));
const document = new EventTarget();
document.readyState = 'complete';
document.getElementById = id => nodes.get(id);
document.createElement = () => new Element();
const intervals = new Map();
const packages = new Map([['draft', { id: 'draft', route: { name: 'Entwurf' }, gpx: '[]', pois: [] }]]);
const context = { document, Date, console,
setInterval(callback, delay) { assert.equal(delay, 30000); intervals.set(1, callback); return 1; },
clearInterval(id) { intervals.delete(id); },
Wegwichtel: { Geo: { current: async () => ({ lat: 50, lon: 10, timestamp: Date.now() }) },
LocalRoutes: {
get: id => packages.get(id),
save: value => packages.set(value.id, value),
appendTrackPoint: (gpx, position) => JSON.stringify([...JSON.parse(gpx), position]),
recordedPositions: JSON.parse,
replaceRecordedPositions: (_gpx, positions) => JSON.stringify(positions)
}
}
};
context.globalThis = context;
await loadI18n(context);
vm.runInNewContext(await fs.readFile(sourcePath, 'utf8'), context, { filename: sourcePath.pathname });
return { context, nodes, intervals, packages, recorder: context.Wegwichtel.RouteRecorder };
}
function activate(node, type = 'pointerup', key) {
const event = new Event(type, { cancelable: true });
if (key) Object.defineProperty(event, 'key', { value: key });
node.dispatchEvent(event);
}
test('recording persists every 30 seconds during an editing session, pauses and finishes', async () => {
const { recorder, packages, intervals, nodes } = await setup();
recorder.start('draft'); await tick();
assert.equal(recorder.status, 'recording');
assert.equal(JSON.parse(packages.get('draft').gpx).length, 1);
await intervals.get(1)();
assert.equal(JSON.parse(packages.get('draft').gpx).length, 2);
activate(nodes.get('route-recording-pause'));
assert.equal(recorder.status, 'paused'); assert.equal(intervals.size, 0);
activate(nodes.get('route-recording-pause'), 'keydown', 'Enter'); await tick();
assert.equal(recorder.status, 'recording');
activate(nodes.get('route-recording-stop'));
assert.equal(recorder.status, 'idle'); assert.equal(intervals.size, 0);
assert.equal(nodes.get('route-recording-control').hidden, true);
});
test('GPS failures remain as empty samples and late results after stopping are discarded', async () => {
const { context, recorder, packages, intervals } = await setup();
context.Wegwichtel.Geo.current = async () => { throw new Error('GPS fehlt'); };
recorder.start('draft'); await tick();
const samples = JSON.parse(packages.get('draft').gpx);
assert.equal(samples.length, 1); assert.equal(samples[0].lat, undefined);
let finish;
context.Wegwichtel.Geo.current = () => new Promise(resolve => { finish = resolve; });
const pending = intervals.get(1)();
recorder.stop(); finish({ lat: 51, lon: 11 }); await pending;
assert.equal(JSON.parse(packages.get('draft').gpx).length, 1);
});
test('storage errors pause recording and deleted drafts cannot be recreated by the recorder', async () => {
const { context, recorder, packages, nodes } = await setup();
context.Wegwichtel.LocalRoutes.save = () => { throw new Error('voll'); };
recorder.start('draft'); await tick();
assert.equal(recorder.status, 'paused');
assert.match(nodes.get('route-recording-status').textContent, /Schaffe Platz/);
packages.delete('draft');
context.document.dispatchEvent(new Event('wegwichtel:local-routes-changed'));
assert.equal(recorder.status, 'idle');
recorder.resume(); assert.equal(recorder.status, 'idle');
});
test('missing recorded locations can be filled manually while newer samples remain intact', async () => {
const { recorder, nodes } = await setup();
const initial = JSON.stringify([{ lat: null, lon: null, timestamp: '2026-10-03T10:00:00Z' }]);
recorder.renderPoints(initial);
const row = nodes.get('editor-recorded-positions').children[0];
row.children[2].value = '50'; row.children[4].value = '10';
const latest = JSON.stringify([...JSON.parse(initial), { lat: 51, lon: 11 }]);
const result = JSON.parse(recorder.editGpx(latest, 'Entwurf'));
assert.equal(result[0].lat, 50); assert.equal(result[1].lat, 51);
row.children[4].value = '';
assert.throws(() => recorder.editGpx(latest, 'Entwurf'));
recorder.renderPoints('[]');
activate(nodes.get('editor-add-position'));
assert.equal(JSON.parse(recorder.editGpx('[]', 'Entwurf')).length, 1);
});
test('ending editing stops sampling, reopening permits recording and a fresh app remains inactive', async () => {
const { recorder, intervals, packages } = await setup();
assert.equal(recorder.status, 'idle');
recorder.beginEditing('draft');
await tick();
assert.equal(intervals.size, 1);
recorder.pause();
recorder.beginEditing('draft');
assert.equal(recorder.status, 'paused');
recorder.endEditing();
assert.equal(recorder.status, 'idle');
assert.equal(recorder.routeId, null);
assert.equal(intervals.size, 0);
assert.equal(JSON.parse(packages.get('draft').gpx).length, 1);
recorder.beginEditing('draft');
await tick();
assert.equal(recorder.status, 'recording');
const fresh = await setup();
assert.equal(fresh.recorder.status, 'idle');
assert.equal(fresh.intervals.size, 0);
});