Add files via upload
This commit is contained in:
1 parent
97a156ce8f
commit
48466999c0
11 files changed
+306
-17
No files matched your search
@@ -5,6 +5,21 @@ Alle wichtigen Änderungen an diesem Projekt werden in dieser Datei dokumentiert
|
||||
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
|
||||
und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [37.5.0] - 2026-07-08
|
||||
|
||||
### Klickbare Kacheln + die restlichen Feature-Ideen
|
||||
|
||||
#### Added - Routenplaner
|
||||
- **Via-Station**: Optionales Feld "Über" mit Autocomplete - Verbindungen laufen dann über diese Zwischenstation (API akzeptiert nur Stop-IDs, der Adapter löst Namen automatisch auf).
|
||||
- **Barrierefrei-Modus** (♿): nutzt das Rollstuhl-Profil für Fußwege und Umstiege.
|
||||
- **Fahrrad-Mitnahme** (🚲): nur Verbindungen, in denen alle Fahrten Radmitnahme erlauben.
|
||||
- **Ticketpreise (experimentell)**: withFares ist aktiviert; liefert der Feed Preisdaten, erscheint ein grünes Preis-Badge an der Verbindung. Defensiv gebaut - ohne Daten erscheint schlicht nichts. (Die MOTIS-Spec markiert das Feature selbst als experimentell.)
|
||||
- **Losgeh-Erinnerung** (🔔): Button an jeder Verbindung, Notification 10 min vor Abfahrt. Ehrliche Einschränkung: funktioniert nur, solange die App/der Tab offen ist - echte Push-Benachrichtigungen bräuchten einen Server.
|
||||
|
||||
#### Changed
|
||||
- **Alle vier Home-Kacheln sind jetzt klickbar** und öffnen ihre View (vorher nur die Entwickler-Kachel); einheitlich über data-view gelöst, der Spezial-Handler der Dev-Kachel ist raus.
|
||||
- **API-Budget-Anzeige im Entwickler-Tab**: zeigt live "X/90 Requests frei (letzte 60s)".
|
||||
|
||||
## [37.4.0] - 2026-07-08
|
||||
|
||||
### Favoriten-Routen, Standort auf der Karte, smarterer Geocode
|
||||
|
||||
+34
-7
@@ -111,28 +111,31 @@
|
||||
|
||||
<!-- Quick Info Cards -->
|
||||
<div class="home-quick-info">
|
||||
<div class="home-info-card">
|
||||
<button class="home-info-card home-info-card-btn" data-view="departures"
|
||||
aria-label="Live-Abfahrten öffnen">
|
||||
<div class="home-info-icon">🚇</div>
|
||||
<div class="home-info-text">
|
||||
<strong>Live-Abfahrten</strong>
|
||||
<p>Echtzeit für alle VBB-Stationen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-info-card">
|
||||
</button>
|
||||
<button class="home-info-card home-info-card-btn" data-view="journey"
|
||||
aria-label="Routenplaner öffnen">
|
||||
<div class="home-info-icon">🗺️</div>
|
||||
<div class="home-info-text">
|
||||
<strong>Routenplaner</strong>
|
||||
<p>Schnellste Verbindungen finden</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-info-card">
|
||||
</button>
|
||||
<button class="home-info-card home-info-card-btn" data-view="livemap"
|
||||
aria-label="Live-Map öffnen">
|
||||
<div class="home-info-icon">🌍</div>
|
||||
<div class="home-info-text">
|
||||
<strong>Live-Map</strong>
|
||||
<p>Fahrzeuge in Echtzeit verfolgen</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="home-info-card home-info-card-btn" id="homeDevCard"
|
||||
</button>
|
||||
<button class="home-info-card home-info-card-btn" data-view="developer"
|
||||
aria-label="Entwickler-Bereich mit Changelog öffnen">
|
||||
<div class="home-info-icon">👨💻</div>
|
||||
<div class="home-info-text">
|
||||
@@ -256,6 +259,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Via-Station (optional) -->
|
||||
<div class="journey-via-row">
|
||||
<label class="journey-label" for="journeyVia">📌 Über (optional)</label>
|
||||
<div class="search-container">
|
||||
<input type="text" id="journeyVia" class="search-input"
|
||||
placeholder="Zwischenstation..." aria-label="Zwischenstation (optional)"
|
||||
autocomplete="off">
|
||||
<button class="clear-btn" id="clearViaBtn" aria-label="Zwischenstation löschen">×</button>
|
||||
<div class="suggestions" id="suggestionsVia"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Routing-Optionen -->
|
||||
<div class="journey-options-row">
|
||||
<label class="journey-label">⚙️ Optionen</label>
|
||||
<div class="filter-chips">
|
||||
<button class="filter-btn journey-option-chip" data-option="wheelchair"
|
||||
aria-pressed="false" title="Nur barrierefreie Verbindungen">♿ Barrierefrei</button>
|
||||
<button class="filter-btn journey-option-chip" data-option="bike"
|
||||
aria-pressed="false" title="Nur Verbindungen mit Fahrrad-Mitnahme">🚲 Fahrrad dabei</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zeitwahl: Abfahrt/Ankunft -->
|
||||
<div class="journey-time-row">
|
||||
<label class="journey-label" for="journeyTimeMode">🕐 Zeit</label>
|
||||
@@ -349,6 +375,7 @@
|
||||
<table class="dev-info-table">
|
||||
<tr><td>Version:</td><td><strong id="devVersion"></strong></td></tr>
|
||||
<tr><td>Release:</td><td id="devRelease"></td></tr>
|
||||
<tr><td>API-Budget:</td><td id="devApiBudget">–</td></tr>
|
||||
<tr><td>Lizenz:</td><td>MIT</td></tr>
|
||||
<tr><td>Status:</td><td><span class="status-badge-dev">✅ Stabil</span></td></tr>
|
||||
</table>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// ZENTRALE VERSIONSNUMMER - einzige Stelle, die bei Releases
|
||||
// angepasst wird. Alle Anzeigen (Startseite, Entwickler-Tab)
|
||||
// lesen von hier; package.json + SW-Cache-Name manuell mitziehen.
|
||||
const APP_VERSION = '37.4.0';
|
||||
const APP_VERSION = '37.5.0';
|
||||
const APP_RELEASE_DATE = '07.07.2026';
|
||||
|
||||
// Seit v35: Transitous (api.transitous.org) ist die einzige
|
||||
|
||||
@@ -1097,6 +1097,8 @@
|
||||
// Journey Planner State
|
||||
let journeyFromStation = null;
|
||||
let journeyToStation = null;
|
||||
let journeyViaStation = null; // optionale Zwischenstation
|
||||
const journeyOptions = { wheelchair: false, bike: false };
|
||||
|
||||
// Journey Autocomplete
|
||||
let journeyFromTimeout = null;
|
||||
@@ -1174,6 +1176,38 @@
|
||||
checkJourneySearchReady();
|
||||
});
|
||||
|
||||
// Via-Station (optional - beeinflusst den Such-Button nicht)
|
||||
const journeyVia = document.getElementById('journeyVia');
|
||||
const suggestionsVia = document.getElementById('suggestionsVia');
|
||||
const clearViaBtn = document.getElementById('clearViaBtn');
|
||||
if (journeyVia && suggestionsVia) {
|
||||
setupJourneyAutocomplete(journeyVia, suggestionsVia, (id, name, lat, lon) => {
|
||||
journeyViaStation = { id, name, lat, lon };
|
||||
});
|
||||
journeyVia.addEventListener('input', () => {
|
||||
// Freitext ohne Auswahl gilt nicht als Via
|
||||
if (journeyVia.value === '') journeyViaStation = null;
|
||||
});
|
||||
if (clearViaBtn) {
|
||||
clearViaBtn.addEventListener('click', () => {
|
||||
journeyVia.value = '';
|
||||
journeyViaStation = null;
|
||||
suggestionsVia.classList.remove('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Options-Chips (Barrierefrei / Fahrrad) togglen
|
||||
document.addEventListener('click', (e) => {
|
||||
const chip = e.target.closest('.journey-option-chip');
|
||||
if (!chip) return;
|
||||
const opt = chip.dataset.option;
|
||||
journeyOptions[opt] = !journeyOptions[opt];
|
||||
chip.classList.toggle('active', journeyOptions[opt]);
|
||||
chip.setAttribute('aria-pressed', String(journeyOptions[opt]));
|
||||
if (navigator.vibrate) navigator.vibrate(5);
|
||||
});
|
||||
|
||||
// Clear buttons
|
||||
clearFromBtn.addEventListener('click', () => {
|
||||
journeyFrom.value = '';
|
||||
@@ -1279,6 +1313,56 @@
|
||||
// Cursor der letzten Suche (für "Früher"/"Später"-Blättern)
|
||||
let journeyPageCursors = { prev: null, next: null };
|
||||
|
||||
// Losgeh-Erinnerung: Notification 10 min vor Abfahrt.
|
||||
// EHRLICHE EINSCHRÄNKUNG: funktioniert nur, solange die App/der Tab
|
||||
// offen ist (echte Push-Benachrichtigungen bräuchten einen Server).
|
||||
const REMINDER_LEAD_MIN = 10;
|
||||
|
||||
async function scheduleReminder(btn) {
|
||||
const depTime = new Date(btn.dataset.dep);
|
||||
const fireAt = depTime.getTime() - REMINDER_LEAD_MIN * 60000;
|
||||
const delay = fireAt - Date.now();
|
||||
|
||||
if (delay <= 0) {
|
||||
btn.textContent = '⏰ zu knapp';
|
||||
setTimeout(() => { btn.textContent = '🔔'; }, 2500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('Notification' in window)) {
|
||||
btn.textContent = '🚫';
|
||||
btn.title = 'Benachrichtigungen werden von diesem Browser nicht unterstützt';
|
||||
return;
|
||||
}
|
||||
|
||||
let permission = Notification.permission;
|
||||
if (permission === 'default') {
|
||||
permission = await Notification.requestPermission();
|
||||
}
|
||||
if (permission !== 'granted') {
|
||||
btn.textContent = '🚫';
|
||||
btn.title = 'Benachrichtigungen sind blockiert (Browser-Einstellungen)';
|
||||
return;
|
||||
}
|
||||
|
||||
const label = btn.dataset.label;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
new Notification('🚇 Zeit loszugehen!', {
|
||||
body: `${label} – Abfahrt in ${REMINDER_LEAD_MIN} Minuten`,
|
||||
tag: 'vbb-reminder-' + btn.dataset.dep
|
||||
});
|
||||
if (navigator.vibrate) navigator.vibrate([100, 50, 100]);
|
||||
} catch (e) { /* Tab evtl. eingefroren */ }
|
||||
}, delay);
|
||||
|
||||
const fireTime = new Date(fireAt).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
btn.textContent = `🔔 ${fireTime} ✓`;
|
||||
btn.classList.add('active');
|
||||
btn.title = `Erinnerung um ${fireTime} gesetzt (solange die App offen bleibt)`;
|
||||
if (navigator.vibrate) navigator.vibrate(10);
|
||||
}
|
||||
|
||||
async function runJourneySearch(pageCursor = null) {
|
||||
journeyContainer.innerHTML = '<div class="loading">⏳ Suche Verbindungen...</div>';
|
||||
|
||||
@@ -1290,6 +1374,9 @@
|
||||
timeISO: (tIn && tIn.value) ? new Date(tIn.value).toISOString() : null,
|
||||
arriveBy: tMode ? tMode.value === 'arrival' : false,
|
||||
products: activeTransportModes,
|
||||
via: journeyViaStation,
|
||||
wheelchair: journeyOptions.wheelchair,
|
||||
bike: journeyOptions.bike,
|
||||
pageCursor
|
||||
});
|
||||
|
||||
@@ -1333,6 +1420,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const remBtn = e.target.closest('.reminder-btn');
|
||||
if (remBtn) {
|
||||
e.stopPropagation(); // Karte darunter nicht öffnen
|
||||
scheduleReminder(remBtn);
|
||||
return;
|
||||
}
|
||||
|
||||
const pagerBtn = e.target.closest('.journey-pager-btn');
|
||||
if (pagerBtn && pagerBtn.dataset.cursor) {
|
||||
if (navigator.vibrate) navigator.vibrate(10);
|
||||
@@ -1531,7 +1625,14 @@
|
||||
→
|
||||
${arrival.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
|
||||
</div>
|
||||
<div class="journey-duration">${durationText}</div>
|
||||
<div class="journey-duration">
|
||||
${journey.fare ? `<span class="journey-fare">🎫 ${journey.fare.amount.toFixed(2).replace('.', ',')} ${journey.fare.currency === 'EUR' ? '€' : escapeHtml(journey.fare.currency)}</span>` : ''}
|
||||
${durationText}
|
||||
<button class="reminder-btn" data-dep="${departure.toISOString()}"
|
||||
data-label="${escapeHtml((journey.legs.find(l => l.line)?.line?.name || 'Verbindung') + ' um ' + departure.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }))}"
|
||||
aria-label="10 Minuten vor Abfahrt erinnern"
|
||||
title="Erinnerung 10 min vor Abfahrt (nur solange die App offen ist)">🔔</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="journey-legs">
|
||||
${legsHtml}
|
||||
|
||||
+14
-4
@@ -419,11 +419,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
setText('devRelease', APP_RELEASE_DATE);
|
||||
setText('devCacheName', 'vbb-status-v' + APP_VERSION);
|
||||
|
||||
// Entwickler-Karte auf der Startseite -> Entwickler-View
|
||||
const devCard = document.getElementById('homeDevCard');
|
||||
if (devCard) {
|
||||
devCard.addEventListener('click', () => switchView('developer'));
|
||||
// Alle Home-Kacheln navigieren per data-view (einheitlich, Delegation)
|
||||
document.addEventListener('click', (e) => {
|
||||
const tile = e.target.closest('.home-info-card-btn[data-view]');
|
||||
if (tile) {
|
||||
if (navigator.vibrate) navigator.vibrate(10);
|
||||
switchView(tile.dataset.view);
|
||||
}
|
||||
});
|
||||
|
||||
// API-Budget-Anzeige im Entwickler-Tab (aktualisiert nur bei offener View)
|
||||
setInterval(() => {
|
||||
if (currentView !== 'developer') return;
|
||||
const el = document.getElementById('devApiBudget');
|
||||
if (el) el.textContent = `${apiBudgetLeft()}/${API_SOFT_LIMIT} Requests frei (letzte 60s)`;
|
||||
}, 2000);
|
||||
|
||||
// Alte Cache-Einträge aufräumen + letzte Position wiederherstellen
|
||||
cleanupStorage();
|
||||
|
||||
+54
-2
@@ -184,6 +184,16 @@ async function transitousDepartures(station, n = 20) {
|
||||
// ROUTENSUCHE (plan -> HAFAS-journeys-Form)
|
||||
// ==========================================
|
||||
|
||||
// Name -> Stop-ID auflösen (z.B. für via, das keine Koordinaten erlaubt)
|
||||
async function transitousResolveStopId(name) {
|
||||
if (!name) return null;
|
||||
const matches = await transitousFetch(
|
||||
`/v1/geocode?text=${encodeURIComponent(name)}&type=STOP&place=${geocodeBias()}&numResults=1`,
|
||||
6 * 60 * 60 * 1000
|
||||
);
|
||||
return (matches && matches[0]) ? matches[0].id : null;
|
||||
}
|
||||
|
||||
// fromPlace/toPlace: MOTIS akzeptiert "lat,lon" ODER Stop-ID
|
||||
async function transitousPlace(station) {
|
||||
if (station.id && station.id.startsWith(TRANSITOUS_ID_PREFIX)) {
|
||||
@@ -200,22 +210,40 @@ async function transitousPlace(station) {
|
||||
return matches[0].id;
|
||||
}
|
||||
|
||||
async function transitousJourneys(fromStation, toStation, { timeISO = null, arriveBy = false, products = null, pageCursor = null } = {}) {
|
||||
async function transitousJourneys(fromStation, toStation, {
|
||||
timeISO = null, arriveBy = false, products = null, pageCursor = null,
|
||||
via = null, wheelchair = false, bike = false
|
||||
} = {}) {
|
||||
const fromPlace = await transitousPlace(fromStation);
|
||||
const toPlace = await transitousPlace(toStation);
|
||||
|
||||
let url = `/v6/plan?fromPlace=${encodeURIComponent(fromPlace)}` +
|
||||
`&toPlace=${encodeURIComponent(toPlace)}` +
|
||||
`&maxTransfers=3&minTransferTime=5`;
|
||||
`&maxTransfers=3&minTransferTime=5` +
|
||||
`&withFares=true`; // Experimentell: Preise, falls der Feed sie liefert
|
||||
if (timeISO) url += `&time=${encodeURIComponent(timeISO)}&arriveBy=${arriveBy}`;
|
||||
if (products) url += `&transitModes=${productsToMotisModes(products)}`;
|
||||
// Blättern: Cursor aus der vorherigen Antwort ("Früher"/"Später")
|
||||
if (pageCursor) url += `&pageCursor=${encodeURIComponent(pageCursor)}`;
|
||||
|
||||
// Via-Station: API akzeptiert NUR Stop-IDs (keine Koordinaten)
|
||||
if (via) {
|
||||
const viaId = via.id && via.id.startsWith(TRANSITOUS_ID_PREFIX)
|
||||
? via.id.slice(TRANSITOUS_ID_PREFIX.length)
|
||||
: await transitousResolveStopId(via.name);
|
||||
if (viaId) url += `&via=${encodeURIComponent(viaId)}`;
|
||||
}
|
||||
|
||||
// Barrierefrei: Rollstuhl-Profil für Fußwege/Umstiege
|
||||
if (wheelchair) url += `&pedestrianProfile=WHEELCHAIR`;
|
||||
// Fahrrad-Mitnahme in allen genutzten Fahrten erforderlich
|
||||
if (bike) url += `&requireBikeTransport=true`;
|
||||
|
||||
// 30s Cache: identische Suche direkt hintereinander = 1 Request
|
||||
const data = await transitousFetch(url, 30000);
|
||||
|
||||
const journeys = (data.itineraries || []).map(it => ({
|
||||
fare: extractFare(it), // null, wenn der Feed keine Preisdaten liefert
|
||||
legs: (it.legs || []).map(leg => {
|
||||
const isWalk = leg.mode === 'WALK' || leg.mode === 'BIKE';
|
||||
return {
|
||||
@@ -252,6 +280,30 @@ async function transitousJourneys(fromStation, toStation, { timeISO = null, arri
|
||||
};
|
||||
}
|
||||
|
||||
// Preis aus der experimentellen Fare-Struktur ziehen - bewusst defensiv:
|
||||
// liefert null bei fehlenden/unerwarteten Daten, dann zeigt die UI nichts.
|
||||
function extractFare(itinerary) {
|
||||
try {
|
||||
let total = 0, currency = null, found = false;
|
||||
for (const ft of (itinerary.fareTransfers || [])) {
|
||||
// Erste Produkt-Alternative pro Transfer nehmen
|
||||
const alternative = (ft.effectiveFareLegProducts || [])[0];
|
||||
if (!alternative) continue;
|
||||
for (const item of alternative) {
|
||||
const product = item && (item.product || item);
|
||||
if (product && typeof product.amount === 'number' && product.amount > 0) {
|
||||
total += product.amount;
|
||||
currency = product.currency || currency;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found ? { amount: total, currency: currency || 'EUR' } : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ==========================================
|
||||
// STATIONEN IN DER NÄHE (map/stops -> HAFAS-nearby-Form)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vbb-netz-status",
|
||||
"version": "37.4.0",
|
||||
"version": "37.5.0",
|
||||
"description": "Live-Nahverkehr für Berlin & Brandenburg - Progressive Web App (PWA) für Echtzeit-Abfahrten, Routenplanung und Live-Fahrzeugverfolgung im VBB-Gebiet",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE_NAME = 'vbb-status-v37.4.0';
|
||||
const CACHE_NAME = 'vbb-status-v37.5.0';
|
||||
|
||||
// App-Shell: nur Dateien, die wirklich existieren!
|
||||
// (Der alte SW listete eine nicht existierende Font-Datei – dadurch
|
||||
|
||||
+48
@@ -4210,3 +4210,51 @@ select:focus-visible {
|
||||
.locate-btn {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* v37.5: Preis-Badge, Erinnerung, Via/Optionen */
|
||||
.journey-fare {
|
||||
background: rgba(76, 175, 80, 0.15);
|
||||
border: 1px solid rgba(76, 175, 80, 0.5);
|
||||
color: #7ED321;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.reminder-btn {
|
||||
background: none;
|
||||
border: 1px solid rgba(255, 237, 0, 0.35);
|
||||
border-radius: 6px;
|
||||
color: #FFED00;
|
||||
padding: 3px 8px;
|
||||
margin-left: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-stack);
|
||||
}
|
||||
|
||||
.reminder-btn:hover,
|
||||
.reminder-btn:focus-visible {
|
||||
background: rgba(255, 237, 0, 0.12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.reminder-btn.active {
|
||||
background: rgba(255, 237, 0, 0.2);
|
||||
border-style: solid;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.journey-via-row,
|
||||
.journey-options-row {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.journey-option-chip[aria-pressed="true"],
|
||||
.journey-option-chip.active {
|
||||
background-color: #FFED00;
|
||||
color: #000;
|
||||
border-color: #FFED00;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ const fixtures = {
|
||||
agencyName: 'BVG', cancelled: false, tripCancelled: false
|
||||
}], place: {}, previousPageCursor: '', nextPageCursor: '' },
|
||||
plan: { previousPageCursor: 'CURSOR_PREV', nextPageCursor: 'CURSOR_NEXT', itineraries: [{
|
||||
fareTransfers: [{ effectiveFareLegProducts: [[ { product: { name: 'Einzelfahrt AB', amount: 3.80, currency: 'EUR' } } ]] }],
|
||||
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 },
|
||||
@@ -212,6 +213,35 @@ const A = global.__a;
|
||||
await A.transitousLocations('teststr2');
|
||||
t('Bias nutzt letzte GPS-Position', requestedUrls[0].includes('place=52.401,13.052'));
|
||||
|
||||
// 14) Routing-Optionen + Via + Preis
|
||||
A.apiCache.clear();
|
||||
requestedUrls.length = 0;
|
||||
const jOpt = await A.transitousJourneys(
|
||||
{ id: 'x', name: 'Start', lat: 52.47, lon: 13.36 },
|
||||
{ id: 'y', name: 'Ziel', lat: 52.52, lon: 13.41 },
|
||||
{ wheelchair: true, bike: true, via: { id: 'transitous:VIA_STOP_1', name: 'Via' } }
|
||||
);
|
||||
const optUrl = requestedUrls.find(u => u.includes('/plan'));
|
||||
t('Barrierefrei in URL', optUrl.includes('pedestrianProfile=WHEELCHAIR'));
|
||||
t('Fahrrad in URL', optUrl.includes('requireBikeTransport=true'));
|
||||
t('Via-Stop-ID in URL (ohne Präfix)', optUrl.includes('via=VIA_STOP_1'));
|
||||
t('withFares immer an', optUrl.includes('withFares=true'));
|
||||
t('Preis defensiv extrahiert (3,80 EUR)',
|
||||
jOpt.journeys[0].fare && jOpt.journeys[0].fare.amount === 3.8 && jOpt.journeys[0].fare.currency === 'EUR');
|
||||
|
||||
// 15) Fehlende Fare-Daten -> fare ist null (keine UI-Anzeige)
|
||||
A.apiCache.clear();
|
||||
const noFare = JSON.parse(JSON.stringify(fixtures.plan));
|
||||
delete noFare.itineraries[0].fareTransfers;
|
||||
const origPlan = fixtures.plan;
|
||||
fixtures.plan = noFare;
|
||||
const jNoFare = await A.transitousJourneys(
|
||||
{ id: 'x2', name: 'S2', lat: 52.4, lon: 13.3 },
|
||||
{ id: 'y2', name: 'Z2', lat: 52.5, lon: 13.4 }, {}
|
||||
);
|
||||
fixtures.plan = origPlan;
|
||||
t('Ohne Fare-Daten: fare === null', jNoFare.journeys[0].fare === null);
|
||||
|
||||
console.log(fails === 0 ? '\n🎉 TRANSITOUS-ADAPTER-TESTS BESTANDEN' : `\n❌ ${fails} fehlgeschlagen`);
|
||||
process.exit(fails ? 1 : 0);
|
||||
})();
|
||||
@@ -271,6 +271,12 @@ setTimeout(() => {
|
||||
else if (!lastJourneyUrl) errors.push('Routen-Chip startet keine Suche');
|
||||
else console.log('✅ Routen-Chip: 1 Tap füllt Felder und sucht sofort');
|
||||
|
||||
// 15) Home-Kacheln navigieren per Klick
|
||||
doc.querySelector('.home-info-card-btn[data-view="livemap"]').click();
|
||||
await new Promise(r => setTimeout(r, 150));
|
||||
if (!doc.getElementById('view-livemap').classList.contains('active')) errors.push('Kachel-Navigation kaputt');
|
||||
else console.log('✅ Home-Kacheln navigieren (Live-Map-Kachel geklickt)');
|
||||
|
||||
console.log('✅ Struktur-Checks abgeschlossen');
|
||||
|
||||
if (errors.length === 0) {
|
||||
|
||||
Reference in new issue
Block a user