update editor and add tests

This commit is contained in:
Florian Zumpe
2026-09-26 20:40:05 +02:00
parent bdc0aabc07
commit 96b379d8d6
14 changed files with 2099 additions and 587 deletions
+63 -29
View File
@@ -9,42 +9,76 @@ async function read(relativePath) {
return fs.readFile(path.join(root, relativePath), 'utf8');
}
test('start page keeps nearby recommendations and the complete route list', async () => {
test('main screen contains a persistent route control next to the information control', async () => {
const html = await read('public/index.html');
const app = await read('public/js/app.js');
const css = await read('public/css/editor.css');
assert.match(html, /id="nearby-route-list"/);
assert.match(html, /id="all-route-list"/);
assert.match(html, /id="route-search"/);
assert.match(app, /renderRouteItems\('#nearby-route-list'/);
assert.match(app, /renderRouteItems\(\s*'#all-route-list'/);
assert.doesNotMatch(app, /state\.routes\s*=\s*state\.routes[\s\S]*?\.filter\(route => route\.proximityM/);
assert.match(html, /class="route-control"/);
assert.match(html, /id="route-control-button"/);
assert.match(html, /id="route-control-dropdown"/);
assert.match(html, /id="route-control-title"/);
assert.match(html, /id="route-control-next"/);
assert.match(html, /id="route-control-server-list"/);
assert.match(html, /id="route-control-local-list"/);
assert.match(html, /id="route-control-new"/);
assert.match(html, /id="info-button"/);
assert.match(css, /\.route-control\s*\{[\s\S]*position:\s*fixed/i);
assert.match(css, /\.route-control\s*\{[\s\S]*right:\s*calc\([\s\S]*--icon-button-size/i);
assert.match(css, /\.route-control-dropdown/);
});
test('route selection renders independently from geolocation and restores after mobile page resume', async () => {
test('route selection separates activated server routes from local browser routes', async () => {
const html = await read('public/index.html');
const app = await read('public/js/app.js');
const loader = await read('public/js/bootstrap-loader.js');
const css = await read('public/css/app.css');
const server = await read('server.js');
const client = await read('public/js/api-client.js');
const api = await read('src/routes/api.js');
assert.match(html, /vendor\/jquery-ui\/jquery-ui-1\.14\.2\.min\.css\?v=0\.12\.14/);
assert.match(html, /css\/app\.css\?v=0\.12\.14/);
assert.match(html, /id="all-routes-summary"[^>]*>Routen werden geladen/);
assert.doesNotMatch(loader, /loadStyle\(/);
assert.doesNotMatch(css, /:has\(/);
assert.match(html, /<header class="has-back-button">/);
assert.match(html, /id="all-route-list"/);
assert.match(html, /id="local-route-list"/);
assert.match(html, />Lokale Strecken</);
const firstRender = app.indexOf('renderRoutes();', app.indexOf('async initialize(context)'));
const locationStart = app.indexOf('await initializeLocation(context);', app.indexOf('async initialize(context)'));
assert.ok(firstRender >= 0 && locationStart > firstRender, 'Routen müssen vor der Standortabfrage gerendert werden.');
assert.match(app, /globalThis\.addEventListener\('pageshow', restoreRouteLists\)/);
assert.match(app, /document\.visibilityState === 'visible'/);
assert.match(app, /Array\.isArray\(response\?\.routes\)/);
assert.match(app, /throw new TypeError\('Die Routen-API hat keine gültige Routenliste geliefert\.'\)/);
assert.match(app, /route\?\.id != null && route\?\.name != null/);
assert.doesNotMatch(app, /route && route\.id/);
assert.match(server, /no-cache, no-store, must-revalidate/);
assert.match(api, /api\.use\(\(req, res, next\)/);
assert.match(client, /activatedOnly:\s*['"]true['"]/);
assert.match(api, /publishedOnly:\s*req\.query\.activatedOnly\s*===\s*['"]true['"]/);
assert.match(app, /renderRouteItems\(\s*['"]#local-route-list['"]/);
assert.match(app, /renderRouteControlList\(\s*['"]#route-control-local-list['"]/);
assert.match(app, /ns\.LocalRoutes\.getAppRoute\(id\)/);
assert.match(app, /\[data-route-select\]/);
});
test('persistent route control displays the next unvisited POI and preserves progress when switching', async () => {
const app = await read('public/js/app.js');
assert.match(app, /function nextPoiMetric\(/);
assert.match(app, /Nächster POI:/);
assert.match(app, /state\.triggered\.has\(poi\.id\)/);
assert.match(app, /function suspendCurrentRoute\(/);
assert.match(app, /persistRouteProgress\(\)/);
assert.match(app, /await selectRoute/);
});
test('local route storage is loaded independently from optional route progress storage', async () => {
const html = await read('public/index.html');
const app = await read('public/js/app.js');
const localRoutes = await read('public/js/local-routes.js');
const localScript = html.indexOf('js/local-routes.js');
const editorScript = html.indexOf('js/editor-ui.js');
assert.ok(localScript >= 0 && editorScript > localScript, 'local route storage must load before the editor');
assert.match(localRoutes, /wegwichtel\.local-routes\.v1/);
assert.match(localRoutes, /routeKey\(id, ['"]gpx['"]\)/);
assert.match(localRoutes, /routeKey\(id, ['"]json['"]\)/);
assert.match(localRoutes, /wegwichtel:local-routes-changed/);
assert.match(app, /wegwichtel:local-routes-changed/);
});
test('route selection remains pointer-oriented and avoids unsupported relational selectors', async () => {
const app = await read('public/js/app.js');
const editor = await read('public/js/editor-ui.js');
assert.match(app, /pointerup/);
assert.match(editor, /pointerup/);
assert.doesNotMatch(editor, /addEventListener\(['"]click['"]/);
assert.doesNotMatch(app, /:has\(/);
});