overhaul gpx creation and usage
Sonarqube Scanner / Prepare environment (push) Successful in 8s
Sonarqube Scanner / Build and test (push) Failing after 22s
Sonarqube Scanner / SonarQube analysis (push) Skipped
Sonarqube Scanner / Deploy to Server (push) Successful in 6s

This commit is contained in:
Florian Zumpe
2026-10-03 12:40:26 +02:00
parent 1cb3977269
commit 171ea88d55
29 changed files with 969 additions and 344 deletions
+71 -4
View File
@@ -234,6 +234,16 @@ async function loadApp({ language = 'de-DE' } = {}) {
const savedStates = [];
const editor = { opened: 0, refreshed: 0 };
const apiCalls = [];
const frames = [];
const animationTimers = new Map();
let animationTimerId = 0;
function finishAnimations() {
while (frames.length) frames.shift()();
for (const [id, callback] of animationTimers) {
animationTimers.delete(id);
callback();
}
}
harness.node('#route-control-dropdown').properties.set('hidden', true);
harness.node('#route-search').value = '';
@@ -284,7 +294,9 @@ async function loadApp({ language = 'de-DE' } = {}) {
Vibration: { Patterns: { ACTIVE_POI: [100] }, start() {}, stop() {} }
},
alert() {},
clearTimeout,
clearTimeout(id) {
if (!animationTimers.delete(id)) clearTimeout(id);
},
history: {
state: null,
back() {},
@@ -292,7 +304,13 @@ async function loadApp({ language = 'de-DE' } = {}) {
replaceState(state) { this.state = state; }
},
scrollTo() {},
setTimeout,
requestAnimationFrame: callback => frames.push(callback),
setTimeout(callback, delay) {
if (delay !== 180) return setTimeout(callback, delay);
const id = --animationTimerId;
animationTimers.set(id, callback);
return id;
},
structuredClone,
globalThis: null
};
@@ -309,6 +327,7 @@ async function loadApp({ language = 'de-DE' } = {}) {
return {
apiCalls,
finishAnimations,
context,
editor,
harness,
@@ -329,6 +348,39 @@ test('English browser settings localize app controls and retain user-authored tr
assert.ok(harness.node('#local-route-list').appended.join('').includes('Lokaler Weg'));
});
test('route menu animates opening and closing and can reopen during closing', async () => {
const setup = await loadApp();
const { context, harness } = setup;
await context.Wegwichtel.App.initialize({ markStep() {} });
const button = new FakeNode('toggle');
const dropdown = harness.node('#route-control-dropdown');
await harness.activate('#route-control-button', button);
assert.equal(dropdown.properties.get('hidden'), false);
assert.equal(dropdown.classes.has('is-open'), false);
setup.finishAnimations();
assert.equal(dropdown.classes.has('is-open'), true);
await harness.activate('#route-control-button', button);
assert.equal(dropdown.classes.has('is-open'), false);
assert.equal(dropdown.properties.get('hidden'), false);
assert.equal(harness.node('#route-control-button').attributes.get('aria-expanded'), 'false');
await harness.activate('#route-control-button', button);
setup.finishAnimations();
assert.equal(dropdown.classes.has('is-open'), true);
assert.equal(dropdown.properties.get('hidden'), false);
await harness.activate('#route-control-button', button);
setup.finishAnimations();
assert.equal(dropdown.properties.get('hidden'), true);
await harness.activate('#route-control-button', button);
harness.fireDocumentKey('Escape');
setup.finishAnimations();
assert.equal(dropdown.properties.get('hidden'), true);
assert.equal(dropdown.classes.has('is-open'), false);
});
test('route control runtime loads server/local routes and supports switching without geolocation', async () => {
const setup = await loadApp();
const { context, harness, apiCalls, editor } = setup;
@@ -352,6 +404,7 @@ test('route control runtime loads server/local routes and supports switching wit
const newButton = new FakeNode('#route-control-new');
await harness.activate('.route-control-actions #route-control-new', newButton);
assert.equal(editor.opened, 1);
setup.finishAnimations();
assert.equal(harness.node('#route-control-dropdown').properties.get('hidden'), true);
const localButton = new FakeNode('local-select');
@@ -409,6 +462,7 @@ test('route control closes on escape and reports local storage failures', async
await harness.activate('#route-control-button', new FakeNode('toggle'));
assert.equal(harness.node('#route-control-dropdown').properties.get('hidden'), false);
harness.fireDocumentKey('Escape');
setup.finishAnimations();
assert.equal(harness.node('#route-control-dropdown').properties.get('hidden'), true);
context.Wegwichtel.LocalRoutes.listAppRoutes = () => { throw new Error('LocalStorage nicht lesbar'); };
@@ -443,7 +497,17 @@ test('tracking triggers each POI once, records media progress and pauses/resumes
await selectServer(setup);
await harness.activate('#start-route', new FakeNode('start'));
assert.equal(harness.node('#pause-route').properties.get('disabled'), false);
const bearingTargets = [];
context.Wegwichtel.Distance.bearing = (position, target) => {
bearingTargets.push(target);
return target.title ? 45 : 120;
};
calls.position({ lat: 50, lon: 10, accuracy: 5, heading: 90, speed: 1 });
assert.equal(harness.node('#route-navigation-arrow').properties.get('hidden'), false);
assert.equal(harness.node('#navigation-arrow').properties.get('hidden'), false);
assert.notEqual(harness.node('#route-navigation-arrow').styles.get('transform'), harness.node('#navigation-arrow').styles.get('transform'));
assert.ok(bearingTargets.some(target => target.title));
assert.ok(bearingTargets.some(target => !target.title));
assert.equal(harness.node('#poi-title').textValue, 'Serverweg Start');
assert.equal(calls.vibrations, 1);
assert.match(harness.node('#navigation-mode').textValue, /Serverweg Ziel/);
@@ -459,10 +523,13 @@ test('tracking triggers each POI once, records media progress and pauses/resumes
await harness.activate('#pause-route', new FakeNode('pause'));
assert.match(harness.node('#tracking-status').textValue, /Du machst eine Pause/);
assert.equal(savedStates.at(-1)[1], 'paused');
assert.equal(harness.node('#route-navigation-arrow').properties.get('hidden'), true);
assert.equal(harness.node('#navigation-arrow').properties.get('hidden'), true);
await harness.activate('#start-route', new FakeNode('resume'));
calls.position({ lat: 50.001, lon: 10.001, accuracy: 5 });
assert.equal(calls.vibrations, 2);
assert.equal(harness.node('#navigation-distance').textValue, '0 m');
assert.equal(harness.node('#navigation-distance').textValue, '–');
assert.equal(harness.node('#route-navigation-mode').textValue, 'Alle Stationen und das Ziel erreicht');
await harness.activate('#stop-route', new FakeNode('stop'));
assert.equal(savedStates.at(-1)[1], 'idle');
assert.equal(harness.node('#navigation-footer').properties.get('hidden'), true);
@@ -515,7 +582,7 @@ test('tracking handles an empty route and orientation permission resolving after
assert.match(harness.node('#poi-list').appended.join(''), /keine Stationen/);
await harness.activate('#start-route', new FakeNode('start'));
calls.position({ lat: 50, lon: 10, accuracy: 10 });
assert.equal(harness.node('#navigation-mode').textValue, 'Der Weg dieser Strecke fehlt.');
assert.equal(harness.node('#route-navigation-mode').textValue, 'Der Weg dieser Strecke fehlt.');
await harness.activate('#pause-route', new FakeNode('pause'));
const stopped = calls.stopped;
grant(true);