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
+16 -13
View File
@@ -1,3 +1,5 @@
import { readAppFile } from '../test-support/i18n.js';
import { loadI18n } from '../test-support/i18n.js';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
@@ -7,7 +9,7 @@ import vm from 'node:vm';
const root = path.resolve(import.meta.dirname, '..');
async function read(relativePath) {
return fs.readFile(path.join(root, relativePath), 'utf8');
return readAppFile(relativePath);
}
async function clientSources() {
@@ -72,6 +74,7 @@ test('mobile navigation footer contains a rotating SVG arrow and exact metre out
test('client bearing calculation points north and east correctly', async () => {
const source = await read('public/js/distance.js');
const context = { Wegwichtel: {} };
await loadI18n(context);
vm.runInNewContext(source, context);
const distance = context.Wegwichtel.Distance;
@@ -96,10 +99,10 @@ test('route controls use accessible icon buttons with start, pause and stop stat
assert.match(html, /id="start-route"[\s\S]*images\/icons\/play\.svg/);
assert.match(html, /id="pause-route"[\s\S]*images\/icons\/pause\.svg/);
assert.match(html, /id="stop-route"[\s\S]*images\/icons\/stop\.svg/);
assert.match(html, /aria-label="Route starten"/);
assert.match(html, /aria-label="Route pausieren"/);
assert.match(html, /aria-label="Route beenden"/);
assert.match(html, /<fieldset>[\s\S]*<legend>Routensteuerung<\/legend>/);
assert.match(html, /aria-label="Strecke starten"/);
assert.match(html, /aria-label="Strecke pausieren"/);
assert.match(html, /aria-label="Strecke beenden"/);
assert.match(html, /<fieldset>[\s\S]*<legend>Unterwegs<\/legend>/);
assert.doesNotMatch(html, /role="group"/);
assert.doesNotMatch(html, /aria-type=/);
assert.match(app, /routeState:\s*'idle'/);
@@ -131,7 +134,7 @@ test('pause and stop control audio and stop returns to route selection', async (
assert.match(audio, /audio\.load\(\)/);
});
test('the selected or triggered Streckenpunkt is highlighted semantically', async () => {
test('the selected or triggered Station is highlighted semantically', async () => {
const app = await read('public/js/app.js');
const css = await read('public/css/app.css');
@@ -141,7 +144,7 @@ test('the selected or triggered Streckenpunkt is highlighted semantically', asyn
assert.match(css, /aktuelle Station/);
});
test('Streckenpunkte have live direction and metre indicators and take navigation priority', async () => {
test('Stationen have live direction and metre indicators and take navigation priority', async () => {
const app = await read('public/js/app.js');
const css = await read('public/css/app.css');
const html = await read('public/index.html');
@@ -150,16 +153,16 @@ test('Streckenpunkte have live direction and metre indicators and take navigatio
assert.match(app, /function updatePoiIndicators\(position\)/);
assert.match(app, /function nearestPendingPoi\(metrics\)/);
assert.match(app, /if \(nextPoi\) showPoiNavigation\(position, nextPoi\)/);
assert.match(app, /Nächster Streckenpunkt:/);
assert.match(app, /Nächste Station:/);
assert.match(app, /find\('small'\)\.text\(`\$\{distance\} m`\)/);
assert.match(app, /find\('svg'\)\.css\('transform'/);
assert.match(css, /\.poi-proximity[\s\S]*grid-column:\s*2/);
assert.match(css, /\.poi-proximity svg[\s\S]*transition:\s*transform/);
assert.match(html, /Richtung zum nächsten Streckenpunkt/);
assert.match(html, /Richtung zum nächsten Station/);
});
test('reaching a Streckenpunkt preloads audio without autoplay and uses the vibration wrapper', async () => {
test('reaching a Station preloads audio without autoplay and uses the vibration wrapper', async () => {
const app = await read('public/js/app.js');
const audio = await read('public/js/audio-player.js');
const html = await read('public/index.html');
@@ -171,14 +174,14 @@ test('reaching a Streckenpunkt preloads audio without autoplay and uses the vibr
assert.doesNotMatch(app, /navigator\.vibrate/);
assert.match(app, /function stopRouteSensors\(\)[\s\S]*ns\.Vibration\.stop\(\)/);
assert.match(audio, /audio\.load\(\)/);
assert.match(html, /<audio id="poi-audio" controls preload="metadata" aria-label="Audiowiedergabe zum Streckenpunkt" hidden>/);
assert.match(html, /<audio id="poi-audio" controls preload="metadata" aria-label="Ton zur Station" hidden>/);
assert.match(audio, /removeAttr\('hidden'\)/);
assert.match(audio, /Date\.now\(\)/);
assert.match(loader, /'vibration'/);
});
test('Streckenpunkt page uses icon-based back and slideshow controls in a single responsive row', async () => {
test('Station page uses icon-based back and slideshow controls in a single responsive row', async () => {
const html = await read('public/index.html');
const css = await read('public/css/app.css');
const loader = await read('public/js/bootstrap-loader.js');
@@ -196,7 +199,7 @@ test('Streckenpunkt page uses icon-based back and slideshow controls in a single
assert.match(css, /#slide-caption[\s\S]*overflow-wrap:\s*anywhere/);
assert.match(css, /#slide-image[\s\S]*object-fit:\s*contain/);
assert.doesNotMatch(css, /#slideshow img\s*\{/);
assert.match(html, /images\/icons\/play\.svg" alt="Route starten"/);
assert.match(html, /images\/icons\/play\.svg" alt="Strecke starten"/);
assert.match(html, /images\/icons\/chevron-left\.svg" alt="Vorheriges Bild"/);
assert.doesNotMatch(loader, /controlgroup/);
});