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
+21 -13
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';
@@ -65,6 +66,7 @@ async function loadStore(filesystem = false) {
caches: memoryCaches(), crypto, URL, Response, Headers, Blob, Date
};
context.globalThis = context;
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/offline-storage.js'), 'utf8'), context, { filename: path.join(root, 'public/js/offline-storage.js') });
return { context, directory, store: context.Wegwichtel.OfflineStore };
}
@@ -120,10 +122,10 @@ for (const filesystem of [false, true]) {
assert.equal(await (await store.match('/image.png?v=99')).text(), 'first');
assert.equal((await store.match('/image.png')).headers.get('Content-Type'), 'image/png');
const failed = await store.create();
await assert.rejects(store.put(failed, '/image.png', new Response('missing', { status: 404 })), /Download fehlgeschlagen/);
await assert.rejects(store.put(failed, '/image.png', new Response('missing', { status: 404 })), /Teil konnte nicht gespeichert werden/);
await store.remove(failed.id);
assert.equal(await (await store.match('/image.png')).text(), 'first');
assert.throws(() => store.key('https://outside.test/image.png'), /dieser App/);
assert.throws(() => store.key('https://outside.test/image.png'), /anderen Seiten/);
const second = await store.create();
await store.put(second, '/image.png', new Response('second'));
await store.commit(second);
@@ -150,6 +152,7 @@ async function workerHarness() {
skipWaiting: async () => {}, clients: { claim: async () => {} }
};
context.self = context;
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/offline-worker.js'), 'utf8'), context, { filename: path.join(root, 'public/offline-worker.js') });
async function request(url, options) {
let response;
@@ -222,6 +225,9 @@ async function managerHarness({ supported = true, saved = false } = {}) {
await store.put(stage, '/', new Response('old'));
await store.commit(stage);
}
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/user-messages.js'), 'utf8'), context, { filename: path.join(root, 'public/js/user-messages.js') });
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/offline-cache.js'), 'utf8'), context, { filename: path.join(root, 'public/js/offline-cache.js') });
return { context, store, elements, downloaded, manager: context.Wegwichtel.OfflineCache };
}
@@ -241,7 +247,7 @@ test('offline switch prepares all resources once, persists opt-in and clears onl
assert.equal(downloaded.length, 3, 'duplicate resources must not be stored twice');
assert.equal(elements.get('offline-cache-toggle').checked, true);
assert.equal(elements.get('offline-cache-refresh').hidden, false);
assert.match(elements.get('offline-cache-status').textContent, /3 Dateien sind offline verfügbar/);
assert.match(elements.get('offline-cache-status').textContent, /Wegwichtel ist für unterwegs bereit/);
assert.ok(await store.match('/image.png'));
await manager.disable();
assert.equal(await store.state(), null);
@@ -257,9 +263,9 @@ test('failed refresh preserves the old snapshot and download can be cancelled wi
assert.equal(elements.get('offline-cache-toggle').checked, true);
const fetchResource = context.fetch;
context.fetch = async (url, options) => url.endsWith('/image.png') ? new Response('missing', { status: 404 }) : fetchResource(url, options);
await assert.rejects(manager.download(), /Download fehlgeschlagen/);
await assert.rejects(manager.download(), /Teil konnte nicht gespeichert werden/);
assert.equal(await (await store.match('/')).text(), 'old');
assert.match(elements.get('offline-cache-status').textContent, /bisherige Offline-Stand/);
assert.match(elements.get('offline-cache-status').textContent, /vorher gespeichert hast/);
let started;
const downloading = new Promise(resolve => { started = resolve; });
context.fetch = (url, options) => new Promise((resolve, reject) => {
@@ -279,14 +285,14 @@ test('offline UI handles unsupported browsers and malformed manifests without en
const unsupported = await managerHarness({ supported: false });
await unsupported.manager.initialize();
assert.equal(unsupported.elements.get('offline-cache-toggle').disabled, true);
assert.match(unsupported.elements.get('offline-cache-status').textContent, /nicht verfügbar/);
assert.match(unsupported.elements.get('offline-cache-status').textContent, /nicht für unterwegs speichern/);
const setup = await managerHarness();
await setup.manager.initialize();
setup.context.fetch = async () => Response.json({ resources: [] });
await assert.rejects(setup.manager.download(), /Ungültige Offline/);
await assert.rejects(setup.manager.download(), /nicht für unterwegs geladen/);
assert.equal(await setup.store.state(), null);
setup.context.fetch = async () => new Response('error', { status: 500 });
await assert.rejects(setup.manager.download(), /Liste der Offline-Dateien/);
await assert.rejects(setup.manager.download(), /nicht für unterwegs geladen/);
assert.equal(setup.elements.get('offline-cache-toggle').checked, false);
});
@@ -300,7 +306,7 @@ for (const filesystem of [false, true]) {
const stage = await store.create();
await store.put(stage, `/api/routes/${id}`, Response.json({ id }));
await store.put(stage, `/api/routes/${id}/gpx`, new Response(`gpx${id}`));
await store.commit(stage, { id, name: `Route ${id}`, pois: [], points: [] });
await store.commit(stage, { id, name: `Strecke ${id}`, pois: [], points: [] });
}
assert.equal((await store.routes()).length, 2);
assert.deepEqual(await (await store.match('/api/routes/1?_=123')).json(), { id: 1 });
@@ -332,7 +338,7 @@ test('explicit route download stores only selected GPX/media and failed refresh
assert.equal(await (await store.match('/api/routes/7/gpx')).text(), 'bytes:' + origin + '/api/routes/7/gpx');
const previous = (await store.routes())[0].id;
context.fetch = async url => url === '/api/routes/7' ? Response.json(route) : new Response('full', { status: 507 });
await assert.rejects(manager.saveRoute(route), /Download fehlgeschlagen/);
await assert.rejects(manager.saveRoute(route), /Teil konnte nicht gespeichert werden/);
assert.equal((await store.routes())[0].id, previous);
await manager.disable();
assert.equal((await store.routes()).length, 1);
@@ -360,6 +366,7 @@ test('API starts from selected offline routes and an empty list for app-only sto
const app = await store.create();
await store.put(app, '/', new Response('shell'));
await store.commit(app);
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/api-client.js'), 'utf8'), context);
const api = context.Wegwichtel.Api;
assert.equal((await api.health()).offline, true);
@@ -388,15 +395,16 @@ test('storage gauge uses browser estimate, separates Local Storage and reports q
navigator: { storage: { estimate: async () => ({ usage: 95 * 1024 * 1024, quota: 100 * 1024 * 1024 }) } },
localStorage: { length: 1, key: () => 'image', getItem: () => 'x'.repeat(2400000) }
};
await loadI18n(context);
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/storage-usage.js'), 'utf8'), context);
const usage = context.Wegwichtel.StorageUsage;
await usage.refresh();
assert.equal(elements.get('[data-storage-site-meter]').value, 95 * 1024 * 1024);
assert.match(elements.get('[data-storage-site-text]').textContent, /95 %.*5 MiB verfügbar.*fast voll/);
assert.match(elements.get('[data-storage-local-text]').textContent, /Richtwert.*tatsächliche Grenze/);
assert.match(elements.get('[data-storage-site-text]').textContent, /95 %.*5,2 MB frei.*Fast voll/);
assert.match(elements.get('[data-storage-local-text]').textContent, /Richtwert.*wirklich hineinpasst/);
usage.reportError({ cause: { name: 'QuotaExceededError' } });
await usage.refresh();
assert.match(elements.get('[data-storage-warning]').textContent, /Speicher voll/);
assert.match(elements.get('[data-storage-warning]').textContent, /Platz ist voll/);
context.navigator.storage.estimate = async () => { throw new Error('Unavailable'); };
await usage.refresh();
assert.equal(elements.get('[data-storage-site-meter]').hidden, true);