Multiple improvements
Sonarqube Scanner / Prepare environment (push) Successful in 10s
Sonarqube Scanner / Build and test (push) Successful in 28s
Sonarqube Scanner / SonarQube analysis (push) Successful in 1m8s
Sonarqube Scanner / Deploy to Server (push) Successful in 8s

This commit is contained in:
2026-10-05 01:18:14 +02:00
parent 2b07e9bc47
commit 49a9de0355
27 changed files with 797 additions and 80 deletions
+71 -1
View File
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseGpx } from '../src/services/gpx.js';
import { parseGpx, gpxWithWaypoints } from '../src/services/gpx.js';
import { SAMPLE_GPX } from '../test-support/gpx-fixtures.js';
test('parseGpx reads synthetic track data without repository example files', () => {
@@ -11,3 +11,73 @@ test('parseGpx reads synthetic track data without repository example files', ()
assert.equal(parsed.points[0].lat, 52.5200);
assert.equal(parsed.points[2].lon, 13.4090);
});
const point = (tag, lat, lon, content = '') => `<${tag} lat="${lat}" lon="${lon}">${content}</${tag}>`;
const gpx = (waypoints, path = '', extra = '') => `<gpx version="1.1" creator="Test" xmlns:wegwichtel="urn:wegwichtel:poi">${waypoints}${path}${extra}</gpx>`;
const track = points => `<trk><trkseg>${points}</trkseg></trk>`;
const locations = points => points.map(({ lat, lon }) => [lat, lon]);
test('waypoints alone become route points in station order and contribute to distance and bounds', () => {
const parsed = parseGpx(gpx(point('wpt', 50, 10) + point('wpt', 50.01, 10.02)));
assert.deepEqual(locations(parsed.points), [[50, 10], [50.01, 10.02]]);
assert.equal(parsed.metrics.pointCount, 2);
assert.ok(parsed.metrics.distanceM > 0);
assert.equal(parsed.metrics.maxLon, 10.02);
assert.match(parsed.normalizedXml, /<wpt\b/);
assert.match(parsed.normalizedXml, /<rtept\b/);
assert.deepEqual(locations(parseGpx(parsed.normalizedXml).points), locations(parsed.points));
});
test('track points and stations both shape the path without dropping nearby station anchors', () => {
const wp = point('wpt', 50, 10.00001) + point('wpt', 50.005, 10.015);
const recorded = point('trkpt', 50, 10) + point('trkpt', 50, 10.00002)
+ point('trkpt', 50, 10.01) + point('trkpt', 50, 10.02);
const parsed = parseGpx(gpx(wp, track(recorded)));
assert.deepEqual(locations(parsed.points), [[50, 10], [50, 10.00001], [50, 10.01], [50.005, 10.015], [50, 10.02]]);
assert.equal(parsed.metrics.maxLat, 50.005);
assert.equal(parsed.points.length, 5);
assert.deepEqual(parsed.points.map(p => p.sequence), [0, 1, 2, 3, 4]);
assert.deepEqual(locations(parseGpx(parsed.normalizedXml).points), locations(parsed.points));
});
test('duplicate station locations do not multiply route points and station order is retained on loops', () => {
const stations = point('wpt', 50, 10.02) + point('wpt', 50, 10.02) + point('wpt', 50, 10.01);
const recorded = [10, 10.01, 10.02, 10.01, 10].map(lon => point('trkpt', 50, lon)).join('');
const parsed = parseGpx(gpx(stations, track(recorded)));
assert.deepEqual(locations(parsed.points), [[50, 10], [50, 10.01], [50, 10.02], [50, 10.01], [50, 10]]);
assert.deepEqual(locations(parseGpx(parsed.normalizedXml).points), locations(parsed.points));
});
test('existing routes and replacement points incorporate stations, including stations before and after the path', () => {
const stations = [9.99, 10.005, 10.02].map(lon => point('wpt', 50, lon)).join('');
const route = `<rte>${point('rtept', 50, 10)}${point('rtept', 50, 10.01)}</rte>`;
const xml = gpx(stations, route);
const parsed = parseGpx(xml);
assert.deepEqual(locations(parsed.points), [[50, 9.99], [50, 10], [50, 10.005], [50, 10.01], [50, 10.02]]);
assert.deepEqual(locations(parseGpx(xml, [{ lat: 50, lon: 10 }, { lat: 50, lon: 10.01 }]).points), locations(parsed.points));
});
test('missing and invalid station coordinates are excluded while GPS gaps remain available for editing', () => {
const stations = '<wpt><name>Ohne Ort</name></wpt>' + point('wpt', 91, 10) + point('wpt', 50, 181) + point('wpt', 50, 10);
const extra = '<extensions><wegwichtel:gpsGap time="2026-10-04T10:00:00Z"/><wegwichtel:pendingWaypoint><name>Später</name></wegwichtel:pendingWaypoint></extensions>';
const parsed = parseGpx(gpx(stations, '', extra));
assert.deepEqual(locations(parsed.points), [[50, 10]]);
assert.match(parsed.normalizedXml, /wegwichtel:gpsGap/);
assert.match(parsed.normalizedXml, /wegwichtel:pendingWaypoint/);
const empty = parseGpx(gpx('', '', extra));
assert.equal(empty.metrics.pointCount, 0);
assert.equal(empty.metrics.minLat, null);
});
test('moving or deleting a station replaces its generated route anchor without removing recorded points', () => {
const original = parseGpx(gpx(point('wpt', 50.01, 10.005), track(point('trkpt', 50, 10) + point('trkpt', 50, 10.01))));
assert.match(original.normalizedXml, /wegwichtel:waypointRoutePoint/);
const updated = parseGpx(gpxWithWaypoints(original.normalizedXml, [{ id: 1, title: 'Neu', lat: 50.02, lon: 10.006 }]));
assert.equal(updated.points.length, 3);
assert.equal(updated.metrics.maxLat, 50.02);
assert.equal(updated.points.some(p => p.lat === 50.01), false);
const removed = parseGpx(gpxWithWaypoints(updated.normalizedXml, []));
assert.deepEqual(locations(removed.points), [[50, 10], [50, 10.01]]);
assert.deepEqual(locations(parseGpx(updated.normalizedXml).points), locations(updated.points));
});