image handling update for route creation
Sonarqube Scanner / Build and analyze (push) Failing after 20s
Sonarqube Scanner / Build and analyze (push) Failing after 20s
This commit is contained in:
+89
-14
@@ -20,7 +20,7 @@ test('editor markup and source describe local draft creation instead of immediat
|
||||
const ui = await read('public/js/editor-ui.js');
|
||||
|
||||
assert.match(html, /id="editor-button"[^>]+aria-label="Streckenverwaltung öffnen"/);
|
||||
assert.match(html, /<img[^>]+src="images\/icons\/route-management\.svg"[^>]+alt=""[^>]*>/);
|
||||
assert.match(html, /id="route-control-button"[\s\S]*?<img[^>]+src="images\/icons\/route-management\.svg"[^>]+alt=""[^>]*>/);
|
||||
assert.doesNotMatch(html, /class="editor-button-icon"/);
|
||||
assert.match(html, /<dialog id="editor-dialog"/);
|
||||
assert.match(html, /id="editor-route-form"/);
|
||||
@@ -36,8 +36,8 @@ test('editor markup and source describe local draft creation instead of immediat
|
||||
assert.doesNotMatch(html, /id="editor-poi-(?:lat|lon)"[^>]+step=/);
|
||||
assert.match(html, /id="editor-poi-media-preview"/);
|
||||
assert.match(html, /id="editor-poi-delete"[^>]+editor-danger-button/);
|
||||
assert.match(html, />Trackpunkte<\/h3>/);
|
||||
assert.match(html, /aria-label="Trackpunkt hinzufügen"/);
|
||||
assert.match(html, />Streckenpunkte<\/h3>/);
|
||||
assert.match(html, /aria-label="Streckenpunkt hinzufügen"/);
|
||||
assert.match(html, /id="editor-route-upload"[^>]+type="button"[^>]+hidden/);
|
||||
assert.doesNotMatch(html, /accept="video\//);
|
||||
assert.match(html, /ausschließlich in diesem Browser/i);
|
||||
@@ -51,13 +51,15 @@ test('editor markup and source describe local draft creation instead of immediat
|
||||
assert.match(css, /\.editor-media-remove/);
|
||||
assert.match(css, /\.editor-danger-button/);
|
||||
assert.match(css, /\.editor-icon-danger/);
|
||||
assert.match(css, /\.editor-button\s+img/);
|
||||
assert.match(css, /\.route-control-button img/);
|
||||
assert.doesNotMatch(css, /\.editor-button-icon/);
|
||||
|
||||
assert.match(ui, /readJson\(['"]\/routes\?activatedOnly=true['"]\)/);
|
||||
assert.match(ui, /ns\.LocalRoutes\.save\(/);
|
||||
assert.match(ui, /ns\.LocalRoutes\.parseWaypoints\(/);
|
||||
assert.match(ui, /ns\.LocalRoutes\.fileToDataUrl\(/);
|
||||
assert.match(ui, /ns\.LocalRoutes\.storeMediaFile\(/);
|
||||
assert.match(ui, /showOpenFilePicker/);
|
||||
assert.match(ui, /requestPermission:\s*true/);
|
||||
assert.match(ui, /ns\.LocalRoutes\.gpxForUpload\(/);
|
||||
assert.match(ui, /move-poi-up/);
|
||||
assert.match(ui, /move-poi-down/);
|
||||
@@ -76,6 +78,7 @@ test('editor markup and source describe local draft creation instead of immediat
|
||||
assert.match(ui, /sendForm\('\/routes',\s*'POST'/);
|
||||
assert.match(ui, /serverRouteId/);
|
||||
assert.match(ui, /position\?\.accuracy/);
|
||||
assert.match(ui, /setHidden\(byId\(ids\.routeGpxGroup\), !routeId\(route\)\)/);
|
||||
});
|
||||
|
||||
class FakeElement extends EventTarget {
|
||||
@@ -213,6 +216,7 @@ function buildEditorContext() {
|
||||
'editor-route-detail-view',
|
||||
'editor-route-form',
|
||||
'editor-route-gpx',
|
||||
'editor-route-gpx-group',
|
||||
'editor-route-id',
|
||||
'editor-route-list',
|
||||
'editor-route-list-view',
|
||||
@@ -271,6 +275,8 @@ function buildEditorContext() {
|
||||
return { ok: false, status: 404, text: async () => `unexpected ${method} ${url}` };
|
||||
}
|
||||
|
||||
const mediaBlobs = new Map();
|
||||
const storedMediaCalls = [];
|
||||
const LocalRoutes = {
|
||||
createPoiId: () => 9001,
|
||||
createRouteId: () => 'local-new',
|
||||
@@ -282,21 +288,48 @@ function buildEditorContext() {
|
||||
type: file.type || 'application/octet-stream'
|
||||
}),
|
||||
get: id => packages.get(String(id)) || null,
|
||||
getWithMedia: async id => {
|
||||
const value = packages.get(String(id));
|
||||
if (!value) return null;
|
||||
return {
|
||||
...value,
|
||||
pois: value.pois.map(poi => ({
|
||||
...poi,
|
||||
audio: poi.audio ? { ...poi.audio, url: 'blob:test-audio' } : null,
|
||||
images: (poi.images || []).map(image => ({ ...image, url: 'blob:test-image' }))
|
||||
}))
|
||||
};
|
||||
},
|
||||
gpxForUpload: routePackage => routePackage.gpx,
|
||||
list: () => [...packages.values()],
|
||||
parseWaypoints: () => [],
|
||||
readFileAsText: file => file.text(),
|
||||
remove: id => packages.delete(String(id)),
|
||||
mediaBlob: async media => mediaBlobs.get(media.mediaKey) || new Blob(['media'], { type: media.type || 'application/octet-stream' }),
|
||||
removeMedia: async media => { if (media?.mediaKey) mediaBlobs.delete(media.mediaKey); },
|
||||
remove: async id => packages.delete(String(id)),
|
||||
storeMediaFile: async (routeId, poiId, kind, mediaId, file, handle = null) => {
|
||||
storedMediaCalls.push({ routeId, poiId, kind, mediaId, file, handle });
|
||||
const mediaKey = `${routeId}:${poiId}:${kind}:${mediaId}`;
|
||||
mediaBlobs.set(mediaKey, file);
|
||||
return {
|
||||
id: String(mediaId),
|
||||
mediaKey,
|
||||
storage: handle ? 'file-handle' : 'indexeddb',
|
||||
name: file.name,
|
||||
size: file.size || 1,
|
||||
type: file.type || 'application/octet-stream'
|
||||
};
|
||||
},
|
||||
save: input => {
|
||||
const id = String(input.id);
|
||||
const now = '2026-09-19T14:00:00.000Z';
|
||||
const pois = input.pois.map((poi, sequence) => ({ ...poi, sequence }));
|
||||
const trackPoints = pois
|
||||
const gpxPoints = pois
|
||||
.map(poi => `<trkpt lat="${poi.lat}" lon="${poi.lon}"><name>${poi.title || ''}</name><extensions><wegwichtel:poiId>${poi.id}</wegwichtel:poiId></extensions></trkpt>`)
|
||||
.join('');
|
||||
const saved = {
|
||||
id,
|
||||
gpx: `<gpx><trk><trkseg>${trackPoints}</trkseg></trk></gpx>`,
|
||||
gpx: `<gpx><trk><trkseg>${gpxPoints}</trkseg></trk></gpx>`,
|
||||
route: { ...input.route, id, updatedAt: now },
|
||||
pois
|
||||
};
|
||||
@@ -315,6 +348,7 @@ function buildEditorContext() {
|
||||
FormData: FakeFormData,
|
||||
globalThis: null,
|
||||
structuredClone,
|
||||
URL: { createObjectURL: () => 'blob:test-preview', revokeObjectURL() {} },
|
||||
Wegwichtel: {
|
||||
App: { currentPosition: () => ({ lat: 50.123456789, lon: 10.987654321, accuracy: 4.321 }) },
|
||||
Config: { apiBase: '/api/' },
|
||||
@@ -322,10 +356,10 @@ function buildEditorContext() {
|
||||
}
|
||||
};
|
||||
context.globalThis = context;
|
||||
return { calls, context, elements, packages, saves };
|
||||
return { calls, context, elements, packages, saves, storedMediaCalls };
|
||||
}
|
||||
|
||||
test('public editor workflow saves new route and GPX track point locally without POSTing a route', async () => {
|
||||
test('public editor workflow saves new route and GPX Streckenpunkt locally without POSTing a route', async () => {
|
||||
const sourcePath = path.join(root, 'public/js/editor-ui.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
const { calls, context, elements, packages, saves } = buildEditorContext();
|
||||
@@ -338,6 +372,7 @@ test('public editor workflow saves new route and GPX track point locally without
|
||||
assert.ok(calls.some(call => call.method === 'GET' && call.url === '/api/routes?activatedOnly=true'));
|
||||
|
||||
await context.Wegwichtel.EditorUi.openNewLocalRoute();
|
||||
assert.equal(elements.get('editor-route-gpx-group').hidden, true);
|
||||
elements.get('editor-route-name').value = 'Neue lokale Strecke';
|
||||
elements.get('editor-route-description').value = 'Nur im Browser';
|
||||
elements.get('editor-route-form').dispatchEvent(submitEvent());
|
||||
@@ -351,6 +386,7 @@ test('public editor workflow saves new route and GPX track point locally without
|
||||
assert.equal(elements.get('editor-status').textContent, 'Speichern erfolgreich.');
|
||||
assert.equal(elements.get('editor-route-upload').hidden, false);
|
||||
assert.equal(elements.get('editor-route-upload').disabled, false);
|
||||
assert.equal(elements.get('editor-route-gpx-group').hidden, false);
|
||||
|
||||
elements.get('editor-add-poi').dispatchEvent(pointerEvent());
|
||||
assert.equal(elements.get('editor-poi-audio').clickCount, 0);
|
||||
@@ -379,9 +415,11 @@ test('public editor workflow saves new route and GPX track point locally without
|
||||
assert.equal(local.pois[0].lat, 50.123456789);
|
||||
assert.equal(local.pois[0].lon, 10.987654321);
|
||||
assert.equal(local.pois[0].triggerRadiusM, 4.321);
|
||||
assert.match(local.pois[0].audio.dataUrl, /^data:audio\/mpeg/);
|
||||
assert.equal(local.pois[0].audio.storage, 'indexeddb');
|
||||
assert.ok(local.pois[0].audio.mediaKey);
|
||||
assert.equal(local.pois[0].images.length, 1);
|
||||
assert.match(local.pois[0].images[0].dataUrl, /^data:image\/jpeg/);
|
||||
assert.equal(local.pois[0].images[0].storage, 'indexeddb');
|
||||
assert.ok(local.pois[0].images[0].mediaKey);
|
||||
assert.match(local.gpx, /<trkpt lat="50\.123456789" lon="10\.987654321">/);
|
||||
|
||||
const pointListBeforeUpload = elements.get('editor-poi-list');
|
||||
@@ -450,7 +488,7 @@ test('public editor workflow saves new route and GPX track point locally without
|
||||
assert.equal(elements.get('editor-button').attributes.get('aria-expanded'), 'false');
|
||||
});
|
||||
|
||||
test('route and track point list expose red delete actions and remove local data', async () => {
|
||||
test('route and Streckenpunkt list expose red delete actions and remove local data', async () => {
|
||||
const sourcePath = path.join(root, 'public/js/editor-ui.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
const { context, elements, packages } = buildEditorContext();
|
||||
@@ -463,7 +501,7 @@ test('route and track point list expose red delete actions and remove local data
|
||||
await tick();
|
||||
|
||||
elements.get('editor-add-poi').dispatchEvent(pointerEvent());
|
||||
elements.get('editor-poi-title').value = 'Trackpunkt zum Löschen';
|
||||
elements.get('editor-poi-title').value = 'Streckenpunkt zum Löschen';
|
||||
elements.get('editor-poi-form').dispatchEvent(submitEvent());
|
||||
await tick();
|
||||
await tick();
|
||||
@@ -539,3 +577,40 @@ test('editing an activated server route uses PUT and never writes activation sta
|
||||
assert.equal('activatedAt' in body, false);
|
||||
assert.equal(elements.get('editor-status').textContent, 'Speichern erfolgreich.');
|
||||
});
|
||||
|
||||
|
||||
test('local media picker keeps File System Access handles for later upload', async () => {
|
||||
const sourcePath = path.join(root, 'public/js/editor-ui.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
const { context, elements, storedMediaCalls } = buildEditorContext();
|
||||
const imageFile = Object.assign(new Blob(['photo'], { type: 'image/jpeg' }), { name: 'foto.jpg' });
|
||||
const handle = {
|
||||
name: 'foto.jpg',
|
||||
async getFile() { return imageFile; }
|
||||
};
|
||||
context.showOpenFilePicker = async options => {
|
||||
assert.equal(options.multiple, true);
|
||||
return [handle];
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: sourcePath });
|
||||
await context.Wegwichtel.EditorUi.openNewLocalRoute();
|
||||
elements.get('editor-route-name').value = 'Handle-Strecke';
|
||||
elements.get('editor-route-form').dispatchEvent(submitEvent());
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
elements.get('editor-add-poi').dispatchEvent(pointerEvent());
|
||||
elements.get('editor-poi-title').value = 'Streckenpunkt mit Bild';
|
||||
elements.get('editor-poi-images-add').dispatchEvent(pointerEvent());
|
||||
await tick();
|
||||
assert.equal(elements.get('editor-poi-images').clickCount, 0);
|
||||
|
||||
elements.get('editor-poi-form').dispatchEvent(submitEvent());
|
||||
await tick();
|
||||
await tick();
|
||||
const imageStore = storedMediaCalls.find(call => call.kind === 'image');
|
||||
assert.ok(imageStore);
|
||||
assert.equal(imageStore.handle, handle);
|
||||
assert.equal(imageStore.file, imageFile);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user