Add files via upload
This commit is contained in:
1 parent
fa38de2a0a
commit
693e208cfd
12 files changed
+950
-192
No files matched your search
@@ -0,0 +1,176 @@
|
||||
// Transitous-Adapter: Normalisierung MOTIS -> HAFAS-Form testen
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const api = fs.readFileSync(path.join(__dirname, '..', 'js', 'api.js'), 'utf8');
|
||||
const adapter = fs.readFileSync(path.join(__dirname, '..', 'js', 'transitous.js'), 'utf8');
|
||||
|
||||
// Minimale Umgebung
|
||||
global.document = { getElementById: () => null, createElement: () => ({ style: {}, setAttribute(){}, prepend(){} }), body: { prepend(){} } };
|
||||
global.localStorage = { _d: {}, getItem(k){ return this._d[k] ?? null; }, setItem(k,v){ this._d[k]=v; }, };
|
||||
global.AbortController = class { constructor(){ this.signal = {}; } abort(){} };
|
||||
|
||||
// MOTIS-Fixtures (Struktur laut OpenAPI v2.10.2)
|
||||
const fixtures = {
|
||||
geocode: [{ type: 'STOP', name: 'S+U Alexanderplatz', id: 'de-DELFI_de:11000:900100003', lat: 52.5215, lon: 13.4112, tokens: [], areas: [], score: 1 }],
|
||||
stoptimes: { stopTimes: [{
|
||||
place: { name: 'S+U Alexanderplatz', stopId: 'de-DELFI_de:11000:900100003', lat: 52.52, lon: 13.41,
|
||||
departure: '2026-07-06T10:05:00+02:00', scheduledDeparture: '2026-07-06T10:03:00+02:00', track: '4' },
|
||||
mode: 'SUBWAY', realTime: true, headsign: 'U Ruhleben', routeShortName: 'U2', tripId: 'trip123',
|
||||
agencyName: 'BVG', cancelled: false, tripCancelled: false
|
||||
}], place: {}, previousPageCursor: '', nextPageCursor: '' },
|
||||
plan: { itineraries: [{
|
||||
duration: 1200, startTime: '2026-07-06T10:00:00+02:00', endTime: '2026-07-06T10:20:00+02:00', transfers: 0, id: 'it1',
|
||||
legs: [
|
||||
{ mode: 'WALK', from: { name: 'Start', lat: 0, lon: 0 }, to: { name: 'S Südkreuz', lat: 0, lon: 0 },
|
||||
startTime: '2026-07-06T10:00:00+02:00', endTime: '2026-07-06T10:04:00+02:00',
|
||||
scheduledStartTime: '2026-07-06T10:00:00+02:00', scheduledEndTime: '2026-07-06T10:04:00+02:00',
|
||||
realTime: false, scheduled: true, duration: 240, legGeometry: {} },
|
||||
{ mode: 'SUBURBAN', from: { name: 'S Südkreuz', track: '2', lat: 0, lon: 0 }, to: { name: 'S+U Alexanderplatz', lat: 0, lon: 0 },
|
||||
startTime: '2026-07-06T10:06:00+02:00', endTime: '2026-07-06T10:20:00+02:00',
|
||||
scheduledStartTime: '2026-07-06T10:05:00+02:00', scheduledEndTime: '2026-07-06T10:19:00+02:00',
|
||||
realTime: true, scheduled: false, duration: 840, routeShortName: 'S2', headsign: 'S Bernau', legGeometry: {} }
|
||||
]
|
||||
}] }
|
||||
};
|
||||
|
||||
function encodePolyline5(points) {
|
||||
let out = '', prevLat = 0, prevLon = 0;
|
||||
const enc = (v) => {
|
||||
let x = v < 0 ? ~(v << 1) : (v << 1), s = '';
|
||||
while (x >= 0x20) { s += String.fromCharCode((0x20 | (x & 0x1f)) + 63); x >>= 5; }
|
||||
return s + String.fromCharCode(x + 63);
|
||||
};
|
||||
for (const [lat, lon] of points) {
|
||||
const la = Math.round(lat * 1e5), lo = Math.round(lon * 1e5);
|
||||
out += enc(la - prevLat) + enc(lo - prevLon);
|
||||
prevLat = la; prevLon = lo;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
fixtures.mapStops = [
|
||||
{ name: 'U Amrumer Str.', stopId: 'de-DELFI_de:11000:900009202', lat: 52.5423, lon: 13.3495 },
|
||||
{ name: 'S Westhafen', stopId: 'de-DELFI_de:11000:900001201', lat: 52.5360, lon: 13.3430 }
|
||||
];
|
||||
fixtures.trip = { duration: 600, startTime: 'x', endTime: 'x', transfers: 0, id: 't', legs: [{
|
||||
mode: 'SUBWAY', realTime: true, scheduled: true, duration: 600, legGeometry: {},
|
||||
startTime: 'x', endTime: 'x', scheduledStartTime: 'x', scheduledEndTime: 'x',
|
||||
from: { name: 'U Ruhleben', stopId: 'A', scheduledDeparture: '2026-07-06T10:00:00+02:00', departure: '2026-07-06T10:01:00+02:00' },
|
||||
to: { name: 'S+U Pankow', stopId: 'B', scheduledArrival: '2026-07-06T10:40:00+02:00', arrival: '2026-07-06T10:41:00+02:00' },
|
||||
intermediateStops: [
|
||||
{ name: 'U Theodor-Heuss-Platz', stopId: 'C', scheduledDeparture: '2026-07-06T10:05:00+02:00', departure: '2026-07-06T10:06:30+02:00' }
|
||||
]
|
||||
}] };
|
||||
// Segment: 2 Punkte, Fahrt läuft JETZT (Start vor 30s, Ende in 30s)
|
||||
const segNow = Date.now();
|
||||
fixtures.mapTrips = [{
|
||||
trips: [{ tripId: 'RT1', routeShortName: 'M10', displayName: 'M10' }],
|
||||
mode: 'TRAM', distance: 1000,
|
||||
from: { name: 'A', lat: 52.5, lon: 13.3 }, to: { name: 'B Endstation', lat: 52.6, lon: 13.4 },
|
||||
departure: new Date(segNow - 30000).toISOString(),
|
||||
arrival: new Date(segNow + 30000).toISOString(),
|
||||
scheduledDeparture: new Date(segNow - 90000).toISOString(),
|
||||
scheduledArrival: new Date(segNow - 30000).toISOString(),
|
||||
realTime: true,
|
||||
polyline: encodePolyline5([[52.5, 13.3], [52.6, 13.4]])
|
||||
}];
|
||||
|
||||
const requestedUrls = [];
|
||||
global.fetch = async (url) => {
|
||||
requestedUrls.push(url);
|
||||
const body = url.includes('/geocode') ? fixtures.geocode
|
||||
: url.includes('/stoptimes') ? fixtures.stoptimes
|
||||
: url.includes('/plan') ? fixtures.plan
|
||||
: url.includes('/map/stops') ? fixtures.mapStops
|
||||
: url.includes('/map/trips') ? fixtures.mapTrips
|
||||
: url.includes('/trip') ? fixtures.trip : {};
|
||||
return { ok: true, json: async () => body };
|
||||
};
|
||||
|
||||
// api.js-Ausschnitte + Adapter in EINEM Scope evaluieren
|
||||
const combined =
|
||||
api.slice(api.indexOf('const API_SOFT_LIMIT'), api.indexOf('/**'))
|
||||
+ api.slice(api.indexOf('async function apiFetch'), api.indexOf('// ==========================================\n // GLOBALES'))
|
||||
+ api.slice(api.indexOf('function showApiOutageBanner'), api.indexOf('// HTML-Escaping'))
|
||||
+ api.slice(api.indexOf('function storageGet'), api.indexOf('// --- Favoriten ---'))
|
||||
+ adapter
|
||||
+ ';global.__a = { transitousLocations, transitousDepartures, transitousJourneys, motisModeToProduct, productsToMotisModes, transitousNearby, transitousTrip, transitousRadarSegments, segmentPositionAt, decodePolyline5, apiCache };';
|
||||
eval(combined);
|
||||
const A = global.__a;
|
||||
|
||||
(async () => {
|
||||
let fails = 0;
|
||||
const t = (name, ok) => { console.log((ok ? '✅' : '❌'), name); if (!ok) fails++; };
|
||||
|
||||
// 1) Locations-Normalisierung
|
||||
const locs = await A.transitousLocations('alex');
|
||||
t('geocode -> hafas locations', locs[0].type === 'stop'
|
||||
&& locs[0].id === 'transitous:de-DELFI_de:11000:900100003'
|
||||
&& locs[0].name === 'S+U Alexanderplatz'
|
||||
&& locs[0].location.latitude === 52.5215);
|
||||
|
||||
// 2) Departures über Transitous-ID
|
||||
const deps = await A.transitousDepartures({ id: 'transitous:de-DELFI_de:11000:900100003', name: 'Alex' });
|
||||
const d = deps.departures[0];
|
||||
t('stoptimes -> hafas departure (Linie/Richtung)', d.line.name === 'U2' && d.line.product === 'subway' && d.direction === 'U Ruhleben');
|
||||
t('Verspätung korrekt (120s)', d.delay === 120);
|
||||
t('when/plannedWhen/Gleis', d.when.includes('10:05') && d.plannedWhen.includes('10:03') && d.platform === '4');
|
||||
|
||||
// 3) Departures über Koordinaten (kein ID-Mapping nötig)
|
||||
requestedUrls.length = 0;
|
||||
await A.transitousDepartures({ id: '900100003', name: 'Alex', lat: 52.52, lon: 13.41 });
|
||||
t('HAFAS-ID + Koordinaten -> center-Query statt Geocode', requestedUrls.length === 1 && requestedUrls[0].includes('center=52.52,13.41'));
|
||||
|
||||
// 4) Departures nur mit Name (alter Favorit) -> Geocode + Cache
|
||||
A.apiCache.clear();
|
||||
requestedUrls.length = 0;
|
||||
await A.transitousDepartures({ id: '900xyz', name: 'S Südkreuz' });
|
||||
t('Nur Name -> geocode + stoptimes (2 Requests)', requestedUrls.length === 2 && requestedUrls[0].includes('/geocode'));
|
||||
A.apiCache.clear();
|
||||
requestedUrls.length = 0;
|
||||
await A.transitousDepartures({ id: '900xyz', name: 'S Südkreuz' });
|
||||
t('ID-Mapping gecacht (nur noch 1 Request)', requestedUrls.length === 1);
|
||||
|
||||
// 5) Journeys-Normalisierung inkl. Filter und Zeit
|
||||
requestedUrls.length = 0;
|
||||
const j = await A.transitousJourneys(
|
||||
{ id: 'x', name: 'Start', lat: 52.47, lon: 13.36 },
|
||||
{ id: 'y', name: 'Ziel', lat: 52.52, lon: 13.41 },
|
||||
{ timeISO: '2026-07-06T08:00:00.000Z', arriveBy: true, products: new Set(['suburban', 'subway']) }
|
||||
);
|
||||
const leg = j.journeys[0].legs[1];
|
||||
t('plan -> hafas journey (Leg-Felder)', leg.line.name === 'S2' && leg.line.product === 'suburban'
|
||||
&& leg.origin.name === 'S Südkreuz' && leg.departurePlatform === '2');
|
||||
t('Walk-Leg markiert', j.journeys[0].legs[0].walking === true && j.journeys[0].legs[0].line === undefined);
|
||||
const planUrl = requestedUrls.find(u => u.includes('/plan'));
|
||||
t('Koordinaten als fromPlace', planUrl.includes('fromPlace=52.47%2C13.36'));
|
||||
t('arriveBy + Zeit in URL', planUrl.includes('arriveBy=true') && planUrl.includes('time='));
|
||||
t('transitModes mit Fähre', planUrl.includes('SUBURBAN') && planUrl.includes('SUBWAY') && planUrl.includes('FERRY') && !planUrl.includes('TRAM'));
|
||||
|
||||
// 6) Nearby: map/stops -> sortiert nach Distanz
|
||||
const near = await A.transitousNearby(52.5423, 13.3495);
|
||||
t('Nearby: nächste Station zuerst + Distanz', near[0].name === 'U Amrumer Str.' && near[0].distance < near[1].distance);
|
||||
|
||||
// 7) Trip-Details: stopovers in hafas-Form
|
||||
const tripRes = await A.transitousTrip('transitous:RT1');
|
||||
const so = tripRes.trip.stopovers;
|
||||
t('Trip: from + intermediate + to = 3 Stopovers', so.length === 3 && so[1].stop.name === 'U Theodor-Heuss-Platz');
|
||||
t('Trip: Verspätung am Zwischenhalt (90s)', so[1].departureDelay === 90);
|
||||
|
||||
// 8) Polyline-Decoder (Roundtrip-Referenzwerte)
|
||||
const pts = A.decodePolyline5('_p~iF~ps|U_ulLnnqC_mqNvxq`@');
|
||||
t('Polyline-Decoder (Google-Referenzbeispiel)',
|
||||
Math.abs(pts[0][0] - 38.5) < 1e-9 && Math.abs(pts[0][1] + 120.2) < 1e-9 && pts.length === 3);
|
||||
|
||||
// 9) Radar-Segmente + Interpolation: Fahrt zur Hälfte -> Mittelpunkt
|
||||
const segs = await A.transitousRadarSegments({ north: 52.7, south: 52.4, west: 13.2, east: 13.6 }, 12);
|
||||
t('Radar: Segment normalisiert', segs.length === 1 && segs[0].lineName === 'M10' && segs[0].product === 'tram');
|
||||
const pos = A.segmentPositionAt(segs[0], Date.now());
|
||||
t('Interpolation: Position ~Mittelpunkt der Strecke',
|
||||
pos && Math.abs(pos.latitude - 52.55) < 0.01 && Math.abs(pos.longitude - 13.35) < 0.01);
|
||||
t('Interpolation: außerhalb des Zeitfensters -> null',
|
||||
A.segmentPositionAt(segs[0], Date.now() + 120000) === null);
|
||||
|
||||
console.log(fails === 0 ? '\n🎉 TRANSITOUS-ADAPTER-TESTS BESTANDEN' : `\n❌ ${fails} fehlgeschlagen`);
|
||||
process.exit(fails ? 1 : 0);
|
||||
})();
|
||||
@@ -0,0 +1,265 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const dir = path.join(__dirname, '..');
|
||||
const html = fs.readFileSync(path.join(dir, 'index.html'), 'utf8');
|
||||
const moduleFiles = ['js/api.js', 'js/transitous.js', 'js/app.js', 'js/livemap.js', 'js/changelog.js', 'js/extras.js'];
|
||||
|
||||
const cleanHtml = html
|
||||
.replace(/<script[^>]*src=[^>]*><\/script>/g, '')
|
||||
.replace(/<link[^>]*>/g, '');
|
||||
|
||||
const errors = [];
|
||||
const dom = new JSDOM(cleanHtml, {
|
||||
url: 'https://localhost:3000/',
|
||||
runScripts: 'outside-only',
|
||||
pretendToBeVisual: true
|
||||
});
|
||||
const { window } = dom;
|
||||
window.addEventListener('error', (e) => errors.push('window.error: ' + e.message));
|
||||
|
||||
// --- Mocks ---
|
||||
let lastJourneyUrl = null;
|
||||
window.fetch = (url) => {
|
||||
const u = String(url);
|
||||
if (u.includes('/plan')) lastJourneyUrl = u;
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(
|
||||
u.includes('/geocode') ? []
|
||||
: u.includes('/stoptimes') ? { stopTimes: [], place: {} }
|
||||
: u.includes('/plan') ? { itineraries: [] }
|
||||
: u.includes('/map/') ? []
|
||||
: {}
|
||||
),
|
||||
text: () => Promise.resolve('# Changelog\n## v35')
|
||||
});
|
||||
};
|
||||
window.navigator.vibrate = () => true;
|
||||
const mm = () => ({ matches: false, addEventListener(){}, removeEventListener(){}, addListener(){}, removeListener(){} });
|
||||
window.matchMedia = mm;
|
||||
const fakeLayer = {
|
||||
addTo: () => fakeLayer, remove: () => {}, bindPopup: () => fakeLayer,
|
||||
on: () => fakeLayer, setLatLng: () => fakeLayer, setPopupContent: () => fakeLayer
|
||||
};
|
||||
window.L = {
|
||||
polyline: () => fakeLayer,
|
||||
map: () => ({
|
||||
setView(){ return this; }, on(){ return this; }, invalidateSize(){}, getZoom: () => 12,
|
||||
getBounds: () => ({ getNorth:()=>52.6, getSouth:()=>52.4, getEast:()=>13.6, getWest:()=>13.2 })
|
||||
}),
|
||||
tileLayer: () => fakeLayer, marker: () => fakeLayer, divIcon: () => ({})
|
||||
};
|
||||
Object.defineProperty(window.navigator, 'serviceWorker', {
|
||||
value: { register: () => Promise.resolve({}) }, configurable: true
|
||||
});
|
||||
|
||||
// --- Module in Ladereihenfolge ausführen ---
|
||||
// Verkettet ausgeführt: entspricht dem gemeinsamen globalen Scope
|
||||
// klassischer <script>-Tags. Test-Hook (__t) nutzt direktes eval,
|
||||
// um auf den Modul-Scope zugreifen zu können.
|
||||
const combined = moduleFiles
|
||||
.map(f => fs.readFileSync(path.join(dir, f), 'utf8'))
|
||||
.join('\n;\n') + '\n;window.__t = (c) => eval(c);';
|
||||
try {
|
||||
window.eval(combined);
|
||||
console.log('✅ Alle 5 Module geladen (verketteter Scope)');
|
||||
} catch (e) {
|
||||
errors.push('Module: ' + e.message);
|
||||
console.log('❌ Module:', e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
window.document.dispatchEvent(new window.Event('DOMContentLoaded', { bubbles: true }));
|
||||
console.log('✅ DOMContentLoaded ohne Fehler');
|
||||
} catch (e) {
|
||||
errors.push('DOMContentLoaded: ' + e.message);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const doc = window.document;
|
||||
|
||||
// 1) Views durchklicken
|
||||
doc.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.click();
|
||||
const target = doc.getElementById('view-' + item.dataset.view);
|
||||
if (!target?.classList.contains('active')) errors.push('View kaputt: ' + item.dataset.view);
|
||||
});
|
||||
console.log('✅ Alle 5 Views schaltbar');
|
||||
|
||||
// 2) Favoriten: toggle + Rendering auf Home
|
||||
window.__t(`
|
||||
currentStationId = '900000100003';
|
||||
currentStationName = 'S+U Alexanderplatz';
|
||||
toggleFavorite(currentStationId, currentStationName);
|
||||
`);
|
||||
const favItems = doc.querySelectorAll('.home-favorite-item');
|
||||
if (favItems.length !== 1) errors.push('Favorit nicht gerendert');
|
||||
else console.log('✅ Favorit gespeichert & auf Home gerendert:', favItems[0].dataset.name);
|
||||
|
||||
// 3) Persistenz: localStorage-Inhalte prüfen
|
||||
const stored = JSON.parse(window.localStorage.getItem('vbb_favorites') || '[]');
|
||||
if (stored.length !== 1 || stored[0].name !== 'S+U Alexanderplatz') errors.push('localStorage Favoriten falsch');
|
||||
else console.log('✅ localStorage enthält Favorit');
|
||||
|
||||
// 4) Favorit-Klick -> Abfahrten-View + Station gesetzt
|
||||
favItems[0]?.click();
|
||||
if (!doc.getElementById('view-departures').classList.contains('active')) errors.push('Favorit-Klick öffnet Abfahrten nicht');
|
||||
else console.log('✅ Favorit-Klick öffnet Abfahrten-View');
|
||||
|
||||
// 5) Journey: Filter + Zeitwahl in der URL
|
||||
window.__t(`
|
||||
journeyFromStation = { id: 'A1', name: 'Start', lat: 52.47, lon: 13.36 };
|
||||
journeyToStation = { id: 'B2', name: 'Ziel', lat: 52.52, lon: 13.41 };
|
||||
activeTransportModes.delete('bus');
|
||||
document.getElementById('journeyTimeInput').value = '2026-07-04T10:30';
|
||||
document.getElementById('journeyTimeMode').value = 'arrival';
|
||||
document.getElementById('searchJourneyBtn').disabled = false;
|
||||
`);
|
||||
doc.getElementById('searchJourneyBtn').click();
|
||||
|
||||
setTimeout(async () => {
|
||||
if (!lastJourneyUrl) errors.push('Journey-Suche feuerte nicht');
|
||||
else {
|
||||
if (lastJourneyUrl.includes('BUS') || !lastJourneyUrl.includes('SUBWAY')) errors.push('Transport-Filter fehlt/falsch in plan-URL');
|
||||
else console.log('✅ Verkehrsmittel-Filter in plan-URL (ohne BUS)');
|
||||
if (!lastJourneyUrl.includes('arriveBy=true') || !lastJourneyUrl.includes('time=')) errors.push('Zeitwahl fehlt in plan-URL');
|
||||
else console.log('✅ Zeitwahl in plan-URL (arriveBy=true)');
|
||||
}
|
||||
|
||||
// 6) Offline-Cache: Abfahrten-Cache in localStorage nach Fehler nutzbar?
|
||||
const cacheOk = window.__t(`
|
||||
saveDeparturesCache('TEST', [{when: new Date().toISOString(), line: {name: 'U2'}}]);
|
||||
const c = loadDeparturesCache('TEST');
|
||||
c && c.departures.length === 1;
|
||||
`);
|
||||
if (!cacheOk) errors.push('Offline-Cache defekt');
|
||||
else console.log('✅ Offline-Abfahrten-Cache funktioniert');
|
||||
|
||||
// 7) Totalausfall: beide APIs down -> Banner erscheint
|
||||
const origFetch = window.fetch;
|
||||
window.fetch = () => Promise.reject(new TypeError('NetworkError'));
|
||||
window.__t(`apiCache.clear();`);
|
||||
window.__t(`apiFetch('https://v6.vbb.transport.rest/locations?query=test&results=1', { ttl: 0 }).catch(() => {})`);
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
const banner = doc.getElementById('apiOutageBanner');
|
||||
if (!banner || banner.style.display === 'none') errors.push('Outage-Banner erscheint nicht');
|
||||
else console.log('✅ Totalausfall beider APIs -> Störungs-Banner erscheint');
|
||||
|
||||
// 8) API erholt sich -> Banner verschwindet
|
||||
window.fetch = () => Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
|
||||
window.__t(`apiFetch('https://v6.vbb.transport.rest/locations?query=test2&results=1', { ttl: 0 }).catch(() => {})`);
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
if (doc.getElementById('apiOutageBanner').style.display !== 'none') errors.push('Banner verschwindet nicht nach Erholung');
|
||||
else console.log('✅ API-Erholung -> Banner verschwindet automatisch');
|
||||
window.fetch = origFetch;
|
||||
|
||||
// 9) Abfahrten über Transitous (einzige Datenquelle)
|
||||
window.fetch = (url) => {
|
||||
const u = String(url);
|
||||
if (u.includes('api.transitous.org') && u.includes('/stoptimes')) {
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({ stopTimes: [{
|
||||
place: { name: 'Testhalt', departure: '2026-07-06T10:05:00+02:00',
|
||||
scheduledDeparture: '2026-07-06T10:03:00+02:00', track: '1' },
|
||||
mode: 'TRAM', headsign: 'Testziel', routeShortName: 'M10',
|
||||
tripId: 'tt1', realTime: true, cancelled: false, tripCancelled: false
|
||||
}], place: {} }) });
|
||||
}
|
||||
return Promise.reject(new TypeError('NetworkError'));
|
||||
};
|
||||
window.__t(`
|
||||
apiCache.clear();
|
||||
currentStationId = '900100003'; currentStationName = 'Teststation';
|
||||
currentStationLat = 52.52; currentStationLon = 13.41;
|
||||
`);
|
||||
window.__t('loadDepartures()');
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
const depHtml = doc.getElementById('departuresContainer').innerHTML;
|
||||
if (!depHtml.includes('M10') || !depHtml.includes('Testziel')) {
|
||||
errors.push('Abfahrten über Transitous werden nicht gerendert');
|
||||
} else console.log('✅ Abfahrten kommen über Transitous (stoptimes -> Renderer)');
|
||||
|
||||
// 10) Stationssuche über Transitous (geocode)
|
||||
window.__t(`apiCache.clear();`);
|
||||
window.fetch = (url) => {
|
||||
const u = String(url);
|
||||
if (u.includes('/geocode')) return Promise.resolve({ ok: true, json: () => Promise.resolve([
|
||||
{ type: 'STOP', name: 'Amrumer Str.', id: 'de-DELFI_de:11000:900009202', lat: 52.542, lon: 13.349, tokens: [], areas: [], score: 1 }
|
||||
]) });
|
||||
return Promise.reject(new TypeError('NetworkError'));
|
||||
};
|
||||
doc.getElementById('stationSearch').value = 'Amrumer';
|
||||
doc.getElementById('stationSearch').dispatchEvent(new window.Event('input', { bubbles: true }));
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
const sugHtml = doc.getElementById('suggestions').innerHTML;
|
||||
if (!sugHtml.includes('Amrumer')) errors.push('Suche über Transitous liefert keine Vorschläge');
|
||||
else console.log('✅ Stationssuche über Transitous (geocode -> Vorschläge)');
|
||||
window.fetch = origFetch;
|
||||
|
||||
// 11) Abfahrten-Filter: filtert wirklich + Auto-Refresh respektiert Filter
|
||||
window.__t(`
|
||||
allDepartures = [];
|
||||
displayDepartures([
|
||||
{ tripId: 't1', direction: 'A', when: new Date(Date.now()+300000).toISOString(), line: { name: 'U2', product: 'subway' } },
|
||||
{ tripId: 't2', direction: 'B', when: new Date(Date.now()+400000).toISOString(), line: { name: '100', product: 'bus' } }
|
||||
]);
|
||||
`);
|
||||
const filterBar = doc.getElementById('departureFilters');
|
||||
if (filterBar.style.display === 'none') errors.push('Filterleiste erscheint nicht');
|
||||
else console.log('✅ Filterleiste erscheint bei Abfahrten');
|
||||
|
||||
doc.querySelector('.departure-filters .filter-btn[data-filter="subway"]').click();
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const shown = doc.querySelectorAll('#departuresContainer .departure-item');
|
||||
const onlySubway = shown.length === 1 && shown[0].dataset.tripId === 't1';
|
||||
if (!onlySubway) errors.push('Abfahrten-Filter filtert nicht (U-Bahn-Filter zeigt ' + shown.length + ' Items)');
|
||||
else console.log('✅ Abfahrten-Filter: U-Bahn-Filter zeigt nur U2');
|
||||
|
||||
// Mehr-laden-Button vorhanden?
|
||||
if (!doc.getElementById('loadMoreDepartures')) errors.push('Mehr-laden-Button fehlt');
|
||||
else console.log('✅ "Mehr Abfahrten laden"-Button gerendert');
|
||||
|
||||
// 12) Versions-Konsistenz: APP_VERSION == package.json == SW-Cache + Anzeigen befüllt
|
||||
const pkgVersion = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version;
|
||||
const appVersion = window.__t('APP_VERSION');
|
||||
const swSrc = fs.readFileSync(path.join(dir, 'service-worker.js'), 'utf8');
|
||||
if (appVersion !== pkgVersion) errors.push(`Version inkonsistent: APP_VERSION=${appVersion} vs package.json=${pkgVersion}`);
|
||||
else if (!swSrc.includes(`vbb-status-v${appVersion}`)) errors.push('SW-Cache-Name passt nicht zu APP_VERSION');
|
||||
else console.log('✅ Version konsistent überall:', appVersion);
|
||||
const hv = doc.getElementById('homeVersion');
|
||||
if (!hv || hv.textContent !== 'v' + appVersion) errors.push('homeVersion nicht befüllt');
|
||||
else console.log('✅ Versions-Anzeigen aus APP_VERSION befüllt');
|
||||
|
||||
// 13) Favoriten-Chips in der Abfahrten-View + kompakte Startseite
|
||||
window.__t(`
|
||||
localStorage.setItem('vbb_favorites', JSON.stringify([
|
||||
{ id: 'f1', name: 'Station Eins' }, { id: 'f2', name: 'Station Zwei' },
|
||||
{ id: 'f3', name: 'Station Drei' }, { id: 'f4', name: 'Station Vier' }
|
||||
]));
|
||||
currentStationId = 'f2';
|
||||
renderFavorites();
|
||||
`);
|
||||
const chips = doc.querySelectorAll('#departureFavorites .fav-chip');
|
||||
const activeChip = doc.querySelector('#departureFavorites .fav-chip.active');
|
||||
if (chips.length !== 4) errors.push(`Abfahrten-Chips: ${chips.length} statt 4`);
|
||||
else if (!activeChip || activeChip.dataset.id !== 'f2') errors.push('Aktiver Chip nicht markiert');
|
||||
else console.log('✅ Favoriten-Chips in Abfahrten-View (4 Chips, aktive markiert)');
|
||||
|
||||
const homeItems = doc.querySelectorAll('#homeFavorites .home-favorite-item:not(.home-favorites-more)');
|
||||
const moreBtn = doc.getElementById('homeShowAllFavs');
|
||||
if (homeItems.length !== 3 || !moreBtn) errors.push('Startseite nicht kompakt (Top 3 + Alle anzeigen)');
|
||||
else console.log('✅ Startseite kompakt: Top 3 + "Alle anzeigen"-Button');
|
||||
|
||||
console.log('✅ Struktur-Checks abgeschlossen');
|
||||
|
||||
if (errors.length === 0) {
|
||||
console.log('\n🎉 ALLE TESTS BESTANDEN');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n❌ FEHLER:');
|
||||
errors.forEach(e => console.log(' -', e));
|
||||
process.exit(1);
|
||||
}
|
||||
}, 300);
|
||||
}, 300);
|
||||
Reference in new issue
Block a user