This commit is contained in:
@@ -64,6 +64,14 @@ class XmlElement {
|
||||
return child;
|
||||
}
|
||||
|
||||
before(...nodes) {
|
||||
if (!this.parentNode) return;
|
||||
const index = this.parentNode.children.indexOf(this);
|
||||
if (index < 0) return;
|
||||
for (const node of nodes) node.parentNode = this.parentNode;
|
||||
this.parentNode.children.splice(index, 0, ...nodes);
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.parentNode) return;
|
||||
this.parentNode.children = this.parentNode.children.filter(child => child !== this);
|
||||
@@ -227,13 +235,13 @@ class FakeFileReader extends EventTarget {
|
||||
readAsDataURL(file) {
|
||||
queueMicrotask(() => {
|
||||
const type = file.type || 'application/octet-stream';
|
||||
this.result = `data:${type};base64,VEVTVA==`;
|
||||
this.result = file.invalidReaderResult ? { type } : `data:${type};base64,VEVTVA==`;
|
||||
this.dispatchEvent(new Event('load'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLocalRoutes() {
|
||||
async function loadLocalRoutes(options = {}) {
|
||||
const sourcePath = path.join(root, 'public/js/local-routes.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
const localStorage = new FakeStorage();
|
||||
@@ -248,7 +256,7 @@ async function loadLocalRoutes() {
|
||||
EventTarget,
|
||||
FileReader: FakeFileReader,
|
||||
XMLSerializer: FakeXMLSerializer,
|
||||
crypto: { randomUUID: () => '11111111-2222-4333-8444-555555555555' },
|
||||
crypto: options.crypto || { randomUUID: () => '11111111-2222-4333-8444-555555555555' },
|
||||
document,
|
||||
globalThis: null,
|
||||
localStorage,
|
||||
@@ -270,6 +278,45 @@ const sampleGpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
</trkseg></trk>
|
||||
</gpx>`;
|
||||
|
||||
test('route id generation uses randomUUID when available', async () => {
|
||||
const { api } = await loadLocalRoutes();
|
||||
|
||||
assert.equal(
|
||||
api.createRouteId(),
|
||||
'local-11111111-2222-4333-8444-555555555555'
|
||||
);
|
||||
});
|
||||
|
||||
test('route id generation falls back to crypto.getRandomValues with UUID v4 bits', async () => {
|
||||
let callCount = 0;
|
||||
const { api } = await loadLocalRoutes({
|
||||
crypto: {
|
||||
getRandomValues(bytes) {
|
||||
callCount += 1;
|
||||
bytes.set([
|
||||
0x00, 0x11, 0x22, 0x33,
|
||||
0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb,
|
||||
0xcc, 0xdd, 0xee, 0xff
|
||||
]);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(api.createRouteId(), 'local-00112233-4455-4677-8899-aabbccddeeff');
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
|
||||
test('route id generation fails when no secure crypto source is available', async () => {
|
||||
const { api } = await loadLocalRoutes({ crypto: {} });
|
||||
|
||||
assert.throws(
|
||||
() => api.createRouteId(),
|
||||
/Keine sichere Zufallsquelle/
|
||||
);
|
||||
});
|
||||
|
||||
test('local route package stores GPX and JSON separately and reconstructs an app route', async () => {
|
||||
const { api, events, localStorage } = await loadLocalRoutes();
|
||||
const routeId = api.createRouteId();
|
||||
@@ -347,6 +394,13 @@ test('GPX waypoint import reads stable POI ids and validates coordinates', async
|
||||
assert.match(api.emptyGpx('A & B'), /<name>A & B<\/name>/);
|
||||
assert.throws(() => api.parseWaypoints('<not-gpx></not-gpx>'), /GPX-Datei ist nicht gültig/);
|
||||
assert.throws(() => api.syncGpx(sampleGpx, { name: 'Fehler' }, [{ id: 1, title: 'Ohne Position' }]), /gültige Koordinaten/);
|
||||
|
||||
const waypointOnly = api.syncGpx(
|
||||
`<gpx version="1.1" xmlns="${GPX_NAMESPACE}"></gpx>`,
|
||||
{ name: 'Nur POI' },
|
||||
[{ id: 2, title: 'Einzelner POI', lat: 50.2, lon: 10.2, sequence: 0 }]
|
||||
);
|
||||
assert.match(waypointOnly, /<wpt lat="50.2" lon="10.2">/);
|
||||
});
|
||||
|
||||
test('remove updates index and dispatches a local-route change event', async () => {
|
||||
@@ -386,16 +440,18 @@ test('quota failure rolls back package writes and keeps the previous package rea
|
||||
assert.equal(api.get('local-quota').route.name, 'Vorher');
|
||||
});
|
||||
|
||||
test('file helpers support native text and FileReader-backed media data URLs', async () => {
|
||||
test('file helpers support Blob.text and FileReader-backed media data URLs', async () => {
|
||||
const { api } = await loadLocalRoutes();
|
||||
|
||||
assert.equal(await api.readFileAsText({ text: async () => '<gpx></gpx>' }), '<gpx></gpx>');
|
||||
assert.equal(await api.readFileAsText(null), '');
|
||||
await assert.rejects(api.readFileAsText({}), { name: 'TypeError' });
|
||||
|
||||
const media = await api.fileToDataUrl({ name: 'ton.mp3', size: 4, type: 'audio/mpeg' });
|
||||
assert.equal(media.name, 'ton.mp3');
|
||||
assert.equal(media.size, 4);
|
||||
assert.equal(media.type, 'audio/mpeg');
|
||||
assert.match(media.dataUrl, /^data:audio\/mpeg;base64,/);
|
||||
await assert.rejects(api.fileToDataUrl({ invalidReaderResult: true }), { name: 'TypeError' });
|
||||
assert.equal(await api.fileToDataUrl(null), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user