editor overhaul
This commit is contained in:
+128
-1
@@ -268,7 +268,7 @@ function buildEditorContext() {
|
||||
if (url === '/api/routes' && method === 'POST') return jsonResponse({ id: 22, name: 'Neue lokale Strecke', pois: [] }, true, 201);
|
||||
if (url === '/api/routes/22/pois' && method === 'POST') return jsonResponse({ id: 23, title: 'Aussicht' }, true, 201);
|
||||
if (/^\/api\/routes\/22\/pois\/23\/(audio|pictures)(?:\/24)?$/.test(url) && (method === 'POST' || method === 'PUT')) return jsonResponse({ id: 24 }, true, 201);
|
||||
if (url === '/api/routes/22' && method === 'GET') return jsonResponse({ id: 22, activatedAt: controls.approved ? '2026-10-03' : null, pois: [{ id: 23, audioUrl: '/audio', images: [{ id: 24 }] }] });
|
||||
if (url === '/api/routes/22?includeDeleted=true' && method === 'GET') return jsonResponse({ id: 22, activatedAt: controls.approved ? '2026-10-03' : null, pois: [{ id: 23, audioUrl: '/audio', images: [{ id: 24 }] }] });
|
||||
if (url === '/api/routes/22' && method === 'PUT') return jsonResponse({ id: 22 });
|
||||
if (url === '/api/routes/22/pois/23' && method === 'PUT') return jsonResponse({ id: 23 });
|
||||
if (/^\/api\/routes\/22\/pois\/23(?:\/audio|\/pictures\/24)?$/.test(url) && method === 'DELETE') return jsonResponse({ deleted: true });
|
||||
@@ -389,6 +389,133 @@ function buildEditorContext() {
|
||||
return { calls, context, elements, packages, saves, storedMediaCalls, controls };
|
||||
}
|
||||
|
||||
async function submittedEditor(response) {
|
||||
const harness = buildEditorContext();
|
||||
const { context, packages, calls, elements } = harness;
|
||||
packages.set('local-existing', {
|
||||
id: 'local-existing', gpx: '<gpx/>',
|
||||
route: { id: 'local-existing', name: 'Waldweg', creator_id: 'a'.repeat(64),
|
||||
serverRouteId: '99', uploadedAt: '2026-10-01T10:00:00Z' },
|
||||
pois: [{ id: 7, title: 'Aussicht', description: 'Wiese', sequence: 0,
|
||||
lat: 50, lon: 10, serverPoiId: 98, serverAudio: true,
|
||||
audio: { name: 'ton.mp3', mediaKey: 'audio-key', type: 'audio/mpeg' },
|
||||
images: [{ id: 'local-image', name: 'bild.png', mediaKey: 'picture-key',
|
||||
type: 'image/png', serverPictureId: 97 }] }]
|
||||
});
|
||||
const originalFetch = context.fetch;
|
||||
context.fetch = async (url, options = {}) => {
|
||||
if (url === '/api/routes/99?includeDeleted=true') {
|
||||
calls.push({ url, method: 'GET' });
|
||||
return response();
|
||||
}
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
await loadI18n(context);
|
||||
const sourcePath = path.join(root, 'public/js/editor-ui.js');
|
||||
vm.runInNewContext(await fs.readFile(sourcePath, 'utf8'), context, { filename: sourcePath });
|
||||
await context.Wegwichtel.EditorUi.open();
|
||||
for (let i = 0; i < 4; i += 1) await tick();
|
||||
const list = elements.get('editor-route-list');
|
||||
const item = list.children.find(child => child.dataset.routeId === 'local-existing');
|
||||
list.dispatchEvent(pointerEvent(item.children[1].children[0]));
|
||||
for (let i = 0; i < 4; i += 1) await tick();
|
||||
return harness;
|
||||
}
|
||||
|
||||
async function submitDraft(harness) {
|
||||
harness.elements.get('editor-route-upload').dispatchEvent(pointerEvent());
|
||||
for (let i = 0; i < 12; i += 1) await tick();
|
||||
}
|
||||
|
||||
test('removed and rejected submissions show their saved status and create fresh route and media IDs', async t => {
|
||||
for (const status of ['removed', 'rejected']) {
|
||||
await t.test(status, async () => {
|
||||
const response = status === 'removed'
|
||||
? () => jsonResponse({ message: 'Strecke nicht gefunden.' }, false, 404)
|
||||
: () => jsonResponse({ id: 99, status: 'deleted', activatedAt: '2026-10-01', pois: [] });
|
||||
const harness = await submittedEditor(response);
|
||||
const { calls, packages, elements } = harness;
|
||||
assert.equal(packages.get('local-existing').route.submissionStatus, status);
|
||||
const expectedLabel = status === 'removed' ? 'Entfernt' : 'Abgelehnt';
|
||||
const item = elements.get('editor-route-list').children.find(child => child.dataset.routeId === 'local-existing');
|
||||
assert.ok(item.children[0].children.some(child => child.textContent === expectedLabel));
|
||||
assert.match(elements.get('editor-route-activated').textContent, status === 'removed' ? /nicht mehr online/ : /abgelehnt/);
|
||||
await submitDraft(harness);
|
||||
const saved = packages.get('local-existing');
|
||||
assert.equal(saved.route.serverRouteId, '22');
|
||||
assert.equal(saved.route.submissionStatus, null);
|
||||
assert.equal(saved.route.creator_id, 'a'.repeat(64));
|
||||
assert.equal(saved.pois[0].serverPoiId, '23');
|
||||
assert.equal(saved.pois[0].images[0].serverPictureId, 24);
|
||||
assert.equal(saved.pois[0].images[0].mediaKey, 'picture-key');
|
||||
assert.equal(saved.pois[0].audio.mediaKey, 'audio-key');
|
||||
assert.equal(elements.get('editor-status').textContent, 'Die Strecke wurde neu angelegt und zur Prüfung gesendet.');
|
||||
assert.deepEqual(calls.filter(call => call.method === 'POST').map(call => call.url), [
|
||||
'/api/routes', '/api/routes/22/pois', '/api/routes/22/pois/23/audio', '/api/routes/22/pois/23/pictures'
|
||||
]);
|
||||
assert.equal(calls.some(call => call.method === 'PUT' || call.method === 'DELETE' || call.url.includes('/restore')), false);
|
||||
await submitDraft(harness);
|
||||
assert.equal(calls.filter(call => call.method === 'POST' && call.url === '/api/routes').length, 1);
|
||||
assert.ok(calls.some(call => call.method === 'PUT' && call.url === '/api/routes/22'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('submission status refresh preserves unsaved form text and clears rejection after restoration', async () => {
|
||||
let status = 'active';
|
||||
const harness = await submittedEditor(() => jsonResponse({ id: 99, status }));
|
||||
harness.elements.get('editor-route-name').value = 'Noch nicht gespeichert';
|
||||
for (const serverStatus of ['deleted', 'active']) {
|
||||
status = serverStatus;
|
||||
await harness.context.Wegwichtel.EditorUi.refresh();
|
||||
for (let i = 0; i < 4; i += 1) await tick();
|
||||
assert.equal(harness.packages.get('local-existing').route.submissionStatus, status === 'deleted' ? 'rejected' : null);
|
||||
assert.equal(harness.elements.get('editor-route-name').value, 'Noch nicht gespeichert');
|
||||
}
|
||||
});
|
||||
|
||||
test('authorization, server and network failures preserve the submitted IDs and never create a route', async t => {
|
||||
for (const status of [401, 403, 500, 'offline']) {
|
||||
await t.test(String(status), async () => {
|
||||
const harness = await submittedEditor(() => {
|
||||
if (status === 'offline') throw new TypeError('Failed to fetch');
|
||||
return jsonResponse({ message: 'Testfehler' }, false, status);
|
||||
});
|
||||
await submitDraft(harness);
|
||||
assert.equal(harness.calls.some(call => call.method !== 'GET'), false);
|
||||
const saved = harness.packages.get('local-existing');
|
||||
assert.equal(saved.route.serverRouteId, '99');
|
||||
assert.equal(saved.pois[0].serverPoiId, 98);
|
||||
assert.equal(saved.pois[0].images[0].serverPictureId, 97);
|
||||
assert.equal(harness.elements.get('editor-route-upload').disabled, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('failed fresh creation preserves local files and retries without obsolete server references', async () => {
|
||||
const harness = await submittedEditor(() => jsonResponse({}, false, 404));
|
||||
const originalFetch = harness.context.fetch;
|
||||
let fail = true;
|
||||
harness.context.fetch = async (url, options = {}) => {
|
||||
if (fail && url === '/api/routes' && options.method === 'POST') {
|
||||
fail = false;
|
||||
return jsonResponse({ message: 'Testfehler' }, false, 500);
|
||||
}
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
await submitDraft(harness);
|
||||
const saved = harness.packages.get('local-existing');
|
||||
assert.equal(saved.route.serverRouteId, null);
|
||||
assert.equal(saved.route.uploadedAt, null);
|
||||
assert.equal(saved.route.submissionStatus, 'removed');
|
||||
assert.equal(saved.pois[0].serverPoiId, undefined);
|
||||
assert.equal(saved.pois[0].serverAudio, undefined);
|
||||
assert.equal(saved.pois[0].images[0].serverPictureId, undefined);
|
||||
assert.equal(saved.pois[0].audio.mediaKey, 'audio-key');
|
||||
await submitDraft(harness);
|
||||
assert.equal(harness.packages.get('local-existing').route.serverRouteId, '22');
|
||||
});
|
||||
|
||||
test('public editor workflow saves new route and GPX Station locally without POSTing a route', async () => {
|
||||
const sourcePath = path.join(root, 'public/js/editor-ui.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user