Internationalization
Sonarqube Scanner / Prepare environment (push) Successful in 20s
Sonarqube Scanner / Build and test (push) Failing after 36s
Sonarqube Scanner / SonarQube analysis (push) Skipped
Sonarqube Scanner / Deploy to Server (push) Successful in 9s

This commit is contained in:
Florian Zumpe
2026-10-01 09:41:01 +02:00
parent 607e5450e1
commit aff88eba1b
41 changed files with 1368 additions and 636 deletions
+24 -22
View File
@@ -1,3 +1,4 @@
import { loadI18n } from '../test-support/i18n.js';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
@@ -345,6 +346,7 @@ async function loadLocalRoutes(options = {}) {
Wegwichtel: {}
};
context.globalThis = context;
await loadI18n(context);
vm.runInNewContext(source, context, { filename: sourcePath });
return { api: context.Wegwichtel.LocalRoutes, events, indexedDB, localStorage };
}
@@ -394,7 +396,7 @@ test('route id generation fails when no secure crypto source is available', asyn
assert.throws(
() => api.createRouteId(),
/Keine sichere Zufallsquelle/
/keine neue Strecke angelegt/
);
});
@@ -405,10 +407,10 @@ test('local route package stores GPX and JSON separately and reconstructs an app
const saved = api.save({
id: routeId,
gpx: api.emptyGpx('Neue Route'),
gpx: api.emptyGpx('Neue Strecke'),
route: {
id: routeId,
name: 'Neue Route',
name: 'Neue Strecke',
description: 'Lokal gespeichert',
serverRouteId: 42,
uploadedAt: '2026-09-27T18:00:00.000Z'
@@ -436,7 +438,7 @@ test('local route package stores GPX and JSON separately and reconstructs an app
]
});
assert.equal(saved.route.name, 'Neue Route');
assert.equal(saved.route.name, 'Neue Strecke');
assert.equal(saved.pois.length, 2);
assert.equal(events.at(-1).action, 'saved');
assert.equal(events.at(-1).id, routeId);
@@ -450,7 +452,7 @@ test('local route package stores GPX and JSON separately and reconstructs an app
assert.match(gpx, /<wegwichtel:poiId>101<\/wegwichtel:poiId>/);
assert.doesNotMatch(gpx, /<wpt/);
assert.equal((gpx.match(/<trkpt /g) || []).length, 2);
assert.equal(metadata.route.name, 'Neue Route');
assert.equal(metadata.route.name, 'Neue Strecke');
assert.equal(metadata.route.serverRouteId, 42);
assert.equal(metadata.route.uploadedAt, '2026-09-27T18:00:00.000Z');
assert.equal(metadata.pois[0].audio.name, 'start.mp3');
@@ -482,19 +484,19 @@ test('GPX waypoint import reads stable POI ids and validates coordinates', async
assert.equal(imported[0].lon, 10.1);
assert.match(api.emptyGpx('A & B'), /<name>A &amp; 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/);
assert.throws(() => api.parseWaypoints('<not-gpx></not-gpx>'), /keinen lesbaren Weg/);
assert.throws(() => api.syncGpx(sampleGpx, { name: 'Fehler' }, [{ id: 1, title: 'Ohne Position' }]), /Ort von Ohne Position fehlt/);
const waypointOnly = api.syncGpx(
`<gpx version="1.1" xmlns="${GPX_NAMESPACE}"></gpx>`,
{ name: 'Nur Streckenpunkt' },
[{ id: 2, title: 'Einzelner Streckenpunkt', lat: 50.2, lon: 10.2, sequence: 0 }]
{ name: 'Nur Station' },
[{ id: 2, title: 'Einzelner Station', lat: 50.2, lon: 10.2, sequence: 0 }]
);
assert.match(waypointOnly, /<trkpt lat="50.2" lon="10.2">/);
assert.doesNotMatch(waypointOnly, /<wpt\b/);
});
test('local points are stored directly as managed GPX Streckenpunkte and uploaded unchanged', async () => {
test('local points are stored directly as managed GPX Stationen and uploaded unchanged', async () => {
const { api } = await loadLocalRoutes();
const saved = api.save({
id: 'local-upload',
@@ -514,10 +516,10 @@ test('local points are stored directly as managed GPX Streckenpunkte and uploade
assert.equal(api.gpxForUpload(saved), saved.gpx, 'upload must use the already stored track without conversion');
});
test('sync keeps imported foreign Streckenpunkte and upload rejects a route without Streckenpunkte', async () => {
test('sync keeps imported foreign Stationen and upload rejects a route without Stationen', async () => {
const { api } = await loadLocalRoutes();
const synced = api.syncGpx(sampleGpx, { name: 'Import' }, [
{ id: 200, title: 'Neuer Streckenpunkt', lat: 50.2, lon: 10.2, sequence: 0 }
{ id: 200, title: 'Neuer Station', lat: 50.2, lon: 10.2, sequence: 0 }
]);
assert.match(synced, /<ele>100<\/ele>/);
@@ -529,7 +531,7 @@ test('sync keeps imported foreign Streckenpunkte and upload rejects a route with
gpx: api.emptyGpx('Leer'),
route: { name: 'Leer' },
pois: []
}), /noch keine Streckenpunkte im GPX-Track/);
}), /noch keinen aufgezeichneten Weg/);
});
test('remove updates index and dispatches a local-route change event', async () => {
@@ -564,7 +566,7 @@ test('quota failure rolls back package writes and keeps the previous package rea
gpx: sampleGpx,
route: { name: 'Nachher' },
pois: []
}), /Speicher für Streckendaten ist voll/);
}), /Platz für deine Strecken ist voll/);
assert.equal(api.get('local-quota').route.name, 'Vorher');
});
@@ -581,7 +583,7 @@ test('local media uses IndexedDB so route JSON does not consume Local Storage qu
id: routeId,
gpx: api.emptyGpx('Medientest'),
route: { name: 'Medientest' },
pois: [{ id: 1, title: 'Streckenpunkt 1', lat: 50.1, lon: 10.1, audio, images: [image] }]
pois: [{ id: 1, title: 'Station 1', lat: 50.1, lon: 10.1, audio, images: [image] }]
});
const metadata = localStorage.getItem(`${api.STORAGE_PREFIX}.${encodeURIComponent(routeId)}.json`);
@@ -640,7 +642,7 @@ test('local media stores a File System Access handle instead of copying file byt
id: 'route-handle',
gpx: api.emptyGpx('Handle'),
route: { name: 'Handle' },
pois: [{ id: 7, title: 'Streckenpunkt', lat: 50.1, lon: 10.1, images: [media] }]
pois: [{ id: 7, title: 'Station', lat: 50.1, lon: 10.1, images: [media] }]
});
const metadata = localStorage.getItem(`${api.STORAGE_PREFIX}.${encodeURIComponent('route-handle')}.json`);
assert.match(metadata, /"storage":"file-handle"/);
@@ -648,12 +650,12 @@ test('local media stores a File System Access handle instead of copying file byt
assert.equal(await api.mediaBlob(media), file);
permission = 'prompt';
await assert.rejects(api.mediaBlob(media), /erneut erlaubt/);
await assert.rejects(api.mediaBlob(media), /Erlaube noch einmal/);
assert.equal(await api.mediaBlob(media, { requestPermission: true }), file);
assert.equal(permissionRequests, 1);
});
test('multiple Streckenpunkte with large local media keep Local Storage metadata small', async () => {
test('multiple Stationen with large local media keep Local Storage metadata small', async () => {
const { api, localStorage } = await loadLocalRoutes();
const routeId = 'multi-media';
const large = new Uint8Array(1024 * 1024);
@@ -664,11 +666,11 @@ test('multiple Streckenpunkte with large local media keep Local Storage metadata
api.save({
id: routeId,
gpx: api.emptyGpx('Mehrere Streckenpunkte'),
route: { name: 'Mehrere Streckenpunkte' },
gpx: api.emptyGpx('Mehrere Stationen'),
route: { name: 'Mehrere Stationen' },
pois: [
{ id: 1, title: 'Erster Streckenpunkt', lat: 50.1, lon: 10.1, images: [first] },
{ id: 2, title: 'Zweiter Streckenpunkt', lat: 50.2, lon: 10.2, images: [second] }
{ id: 1, title: 'Erster Station', lat: 50.1, lon: 10.1, images: [first] },
{ id: 2, title: 'Zweiter Station', lat: 50.2, lon: 10.2, images: [second] }
]
});