From bbb183470988c93a4aaaf719e5344b1efd049dcd Mon Sep 17 00:00:00 2001 From: Florian Zumpe Date: Fri, 2 Oct 2026 10:39:21 +0200 Subject: [PATCH] remove obsolete elements and cleanup gui --- public/css/app.css | 56 +++++++++++++++++++++++++- public/css/editor.css | 41 ------------------- public/index.html | 12 ++---- public/js/app.js | 46 --------------------- public/js/legal-ui.js | 4 +- public/js/texts-de.js | 10 ++--- test/app-route-control-runtime.test.js | 12 ++---- test/legal-ui.test.js | 27 ++++++++++++- test/route-selection-ui.test.js | 11 +++-- 9 files changed, 101 insertions(+), 118 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 546489d..1eaef60 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -546,11 +546,13 @@ button, transition-duration: .01ms !important; } } -.info-button { +.info-control { position: fixed; top: calc(env(safe-area-inset-top) + .45rem); right: max(.65rem, env(safe-area-inset-right)); z-index: 10001; +} +.info-button { display: grid; place-items: center; width: var(--icon-button-size); @@ -571,6 +573,58 @@ button, outline: .2rem solid #fff; outline-offset: .15rem; } +.info-dropdown { + position: absolute; + top: calc(100% + .45rem); + right: 0; + display: grid; + gap: .35rem; + width: min(20rem, calc(100vw - 1.3rem - env(safe-area-inset-left) - env(safe-area-inset-right))); + max-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - var(--icon-button-size) - 1.8rem); + box-sizing: border-box; + padding: .65rem; + overflow-y: auto; + overscroll-behavior: contain; + border: 1px solid rgba(49, 93, 58, .28); + border-radius: .8rem; + background: #f8f5eb; + color: #233326; + box-shadow: 0 .8rem 2rem rgba(20, 32, 22, .28); + transform-origin: top right; + transform: scaleY(0); + opacity: 0; + pointer-events: none; + transition: transform 180ms ease-out, opacity 180ms ease-out; + touch-action: pan-y; +} +.info-dropdown[hidden] { + display: none; +} +.info-dropdown.is-open { + transform: scaleY(1); + opacity: 1; + pointer-events: auto; +} +.info-dropdown button { + width: 100%; + min-height: 2.75rem; + padding: .65rem .8rem; + border: 1px solid #c8d3ca; + border-radius: .55rem; + background: #fff; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + touch-action: manipulation; +} +.info-dropdown button:hover { + background: #e6efdf; +} +.info-dropdown button:focus-visible { + outline: .2rem solid var(--primary-dark); + outline-offset: -.2rem; +} .app-page > header:not(.has-back-button) h1 { padding-right: calc(var(--icon-button-size) + 1rem); diff --git a/public/css/editor.css b/public/css/editor.css index 2cccfc9..a26889d 100644 --- a/public/css/editor.css +++ b/public/css/editor.css @@ -8,43 +8,6 @@ gap: .4rem; } -.route-control-summary { - display: grid; - width: min(13rem, calc(100vw - (var(--icon-button-size) * 2) - 6rem)); - min-height: var(--icon-button-size); - box-sizing: border-box; - align-content: center; - gap: .08rem; - padding: .35rem .75rem; - overflow: hidden; - border: 1px solid rgba(255, 255, 255, .72); - border-radius: .9rem; - background: rgba(36, 70, 43, .94); - color: #fff; - box-shadow: 0 .15rem .55rem rgba(0, 0, 0, .22); - pointer-events: none; -} - -.route-control-summary strong, -.route-control-summary small { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.route-control-summary strong { - font-size: .9rem; - line-height: 1.1; -} - -.route-control-summary small { - color: rgba(255, 255, 255, .86); - font-size: .68rem; - line-height: 1.1; -} - .route-control-button, .route-control-button.ui-button { display: grid; @@ -583,10 +546,6 @@ right: calc(max(.5rem, env(safe-area-inset-right)) + var(--icon-button-size) + .35rem); } - .route-control-summary { - width: min(10.5rem, calc(100vw - (var(--icon-button-size) * 2) - 5rem)); - } - .route-control-dropdown { position: fixed; top: calc(env(safe-area-inset-top) + var(--icon-button-size) + .9rem); diff --git a/public/index.html b/public/index.html index 36cf5a6..4fbdf07 100644 --- a/public/index.html +++ b/public/index.html @@ -128,7 +128,7 @@

Öffne eine Strecke und tippe auf „Für unterwegs speichern“. So nimmst du den Weg, die Stationen, Bilder und Tonaufnahmen mit.

Warte vor dem Losgehen, bis alles gespeichert ist. Zum Speichern und Erneuern brauchst du Internet.

Alles bleibt auf diesem Gerät. Der Schalter zeichnet deinen Weg nicht auf. Beim Ausschalten bleiben deine gespeicherten Strecken und Entwürfe erhalten.

-

Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetprogramm löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen.

+

Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetbrowser löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen.

@@ -159,10 +159,6 @@
-
- Strecke wählen - Strecken für alle und meine Entwürfe -
@@ -309,7 +305,7 @@ Staatliches Schulamt Südthüringen
Datenschutzbeauftragter
Hölderlinstraße 1
98527 Suhl
Telefon: 03681 / 7341 25
Fax: 03681 / 7341 09
E-Mail: datenschutz.suedthueriungen@schulamt.thueringen.de

Der Datenschutzbeauftragte kann außerdem über die Postadresse der Schule mit dem Zusatz „der Datenschutzbeauftragte“ erreicht werden.

2. Wenn du Wegwichtel öffnest

-

Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetprogramm und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.

+

Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetbrowser und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.

Wer die Website bereitstellt und welche Verbindungsdaten wie lange gespeichert werden:[Anbieter, Anschrift, gespeicherte Daten und Speicherdauer ergänzen]

3. Standortdaten

Du entscheidest, ob Wegwichtel deinen Standort nutzen darf. Damit findest du Strecken in deiner Nähe und siehst den Weg zur nächsten Station. Dein laufender Standort wird auf deinem Gerät verarbeitet. Wegwichtel speichert daraus keinen Verlauf auf der Website.

@@ -326,7 +322,7 @@

5. Deine eigenen Strecken

Neue Strecken bleiben zuerst auf deinem Gerät. Gespeichert werden der Weg, die Orte der Stationen, Texte sowie ausgewählte Bilder und Tonaufnahmen.

-

Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.

+

Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetbrowser entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.

6. Werbung und gespeicherte Einstellungen

Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.

7. Inhalte von Schülerinnen und Schülern

@@ -374,7 +370,7 @@

Mit „Weg als Datei herunterladen“ kannst du den Weg in einer anderen Wander-App öffnen. Bilder und Tonaufnahmen sind in dieser Datei nicht enthalten.

Deine eigenen Strecken

Neue Strecken bleiben zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ reichst du eine Strecke zur Freigabe ein. Erst nach der Freigabe können andere sie sehen.

-

Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.

+

Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetbrowser entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.

Welche Erlaubnisse braucht die App?

  • Deinen Standort, um Strecken in deiner Nähe und den Weg zur nächsten Station zu zeigen.
  • diff --git a/public/js/app.js b/public/js/app.js index 4710277..7b0792c 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -138,42 +138,6 @@ items.forEach(route => list.append(routeButton(route, source))); } - function nextPoiMetric() { - const pois = state.route?.pois || []; - const pending = pois - .filter(poi => !state.triggered.has(poi.id)) - .slice() - .sort((left, right) => Number(left.sequence ?? 0) - Number(right.sequence ?? 0)); - if (!pending.length) return null; - if (!state.position) return { poi: pending[0], distance: null }; - return nearestPendingPoi(poiMetrics(state.position)); - } - - function setRouteControlSummary(titleText, detailText) { - $('#route-control-title').text(titleText); - $('#route-control-next').text(detailText); - $('.route-control-summary').attr('aria-label', `${titleText}. ${detailText}`); - } - - function updateRouteControlSummary() { - if (!state.route) { - setRouteControlSummary(ns.I18n.t("app.choose-route"), ns.I18n.t("app.route-menu")); - return; - } - - const titleText = state.route.name || ns.I18n.t("app.route"); - const metric = nextPoiMetric(); - if (!metric) { - const detailText = (state.route.pois || []).length - ? ns.I18n.t("app.stations-complete") - : ns.I18n.t("app.no-stations"); - setRouteControlSummary(titleText, detailText); - return; - } - const distance = metric.distance == null ? '' : ` · ${Math.round(metric.distance)} m`; - setRouteControlSummary(titleText, ns.I18n.t("app.next-station-summary", { value1: metric.poi.title, value2: distance })); - } - function renderRouteControlList(selector, routes, source, emptyText) { const list = $(selector).empty(); if (!routes.length) { @@ -202,7 +166,6 @@ $('#route-control-local-title').text(ns.I18n.t("app.draft-count", { value1: state.localRoutes.length })); $('#local-route-title').text(ns.I18n.t("app.draft-count", { value1: state.localRoutes.length })); - updateRouteControlSummary(); renderRouteControlList( '#route-control-server-list', state.routes, @@ -341,11 +304,8 @@ updateRouteControls(); if (returnToRoutes) { state.route = null; - updateRouteControlSummary(); renderRouteControl(); navigate('#routes-page', { replace: true }); - } else { - updateRouteControlSummary(); } } @@ -360,7 +320,6 @@ .text(ns.I18n.t("app.pause-hint")); showNavigation(ns.I18n.t("app.route-paused"), $('#navigation-distance').text() || '–'); updateRouteControls(); - updateRouteControlSummary(); } function suspendCurrentRoute() { @@ -385,7 +344,6 @@ const list = $('#poi-list').empty(); if (!route.pois.length) { list.append(("
  • " + escapeHtml(ns.I18n.t("app.empty-route")) + "

  • ")); - updateRouteControlSummary(); return; } route.pois.forEach(poi => { @@ -405,7 +363,6 @@ `); }); updatePoiIndicators(state.position); - updateRouteControlSummary(); } function renderRoute(route) { @@ -602,7 +559,6 @@ renderRouteControl(); if (!state.route) return; const metrics = updatePoiIndicators(position); - updateRouteControlSummary(); if (state.routeState !== 'running') return; const nearest = nearestPendingPoi(metrics); @@ -617,7 +573,6 @@ ns.Vibration.start(ns.Vibration.Patterns.ACTIVE_POI); showPoi(nearest.poi); updateNavigation(position); - updateRouteControlSummary(); } } @@ -633,7 +588,6 @@ state.deviceHeading = null; persistRouteProgress(); updateRouteControls(); - updateRouteControlSummary(); $('#tracking-status') .prop('hidden', false) .text(resumed ? ns.I18n.t("app.resuming") : ns.I18n.t("app.location.start")); diff --git a/public/js/legal-ui.js b/public/js/legal-ui.js index a7f32c7..af812a1 100644 --- a/public/js/legal-ui.js +++ b/public/js/legal-ui.js @@ -386,7 +386,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; function openDropdown() { setHidden(elements.dropdown, false); - runNextFrame(() => toggleClass(elements.dropdown, 'is-open', true)); + runNextFrame(() => runNextFrame(() => { + if (dropdownOpen) toggleClass(elements.dropdown, 'is-open', true); + })); } function closeDropdown() { diff --git a/public/js/texts-de.js b/public/js/texts-de.js index 77aa701..45abb41 100644 --- a/public/js/texts-de.js +++ b/public/js/texts-de.js @@ -285,7 +285,7 @@ globalThis.Wegwichtel ??= {}; "save-route": "Öffne eine Strecke und tippe auf „Für unterwegs speichern“. So nimmst du den Weg, die Stationen, Bilder und Tonaufnahmen mit.", "connection": "Warte vor dem Losgehen, bis alles gespeichert ist. Zum Speichern und Erneuern brauchst du Internet.", "keep-routes": "Alles bleibt auf diesem Gerät. Der Schalter zeichnet deinen Weg nicht auf. Beim Ausschalten bleiben deine gespeicherten Strecken und Entwürfe erhalten.", - "storage-limits": "Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetprogramm löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen." + "storage-limits": "Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetbrowser löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen." }, "renew-app": "Gespeicherte App erneuern", "route-controls-label": "Strecke auf dem Gerät speichern", @@ -320,7 +320,7 @@ globalThis.Wegwichtel ??= {}; "site-warning": " Fast voll: Entferne Inhalte, die du nicht mehr brauchst.", "site-usage": "App, Bilder und Ton: {value1} % des verfügbaren Platzes belegt. Ungefähr {value2} frei.{value3}", "local-warning": " Der Balken ist fast voll. Große Bilder können das Speichern verhindern.", - "local-usage": "Texte und Einstellungen: ungefähr {value1} belegt. Der Balken zeigt einen Richtwert von etwa 5 MB. Wie viel wirklich hineinpasst, hängt von deinem Internetprogramm ab.{value2}", + "local-usage": "Texte und Einstellungen: ungefähr {value1} belegt. Der Balken zeigt einen Richtwert von etwa 5 MB. Wie viel wirklich hineinpasst, hängt von deinem Internetbrowser ab.{value2}", "title": "Platz für Wegwichtel", "site-meter-label": "Belegter Platz für App, Bilder und Ton", "loading": "Wir schauen, wie viel Platz belegt ist …", @@ -372,7 +372,7 @@ globalThis.Wegwichtel ??= {}; "incomplete-notice": "Diese Angaben sind noch nicht vollständig. Die Kontakte stammen von der Grundschule Herpf. Wer die Website betreibt, welche Verbindungsdaten gespeichert werden und wie lange sie bleiben, muss noch ergänzt werden.", "controller-title": "1. Verantwortliche Stelle und Datenschutzkontakt", "connection-title": "2. Wenn du Wegwichtel öffnest", - "connection-data": "Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetprogramm und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.", + "connection-data": "Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetbrowser und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.", "provider-label": "Wer die Website bereitstellt und welche Verbindungsdaten wie lange gespeichert werden:", "provider-placeholder": "[Anbieter, Anschrift, gespeicherte Daten und Speicherdauer ergänzen]", "location-title": "3. Standortdaten", @@ -382,7 +382,7 @@ globalThis.Wegwichtel ??= {}; "progress-deletion": "Wenn du deinen Fortschritt mindestens einen Tag lang nicht verändert hast, wird er beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine eigenen und für unterwegs gespeicherten Strecken bleiben erhalten.", "drafts-title": "5. Deine eigenen Strecken", "draft-data": "Neue Strecken bleiben zuerst auf deinem Gerät. Gespeichert werden der Weg, die Orte der Stationen, Texte sowie ausgewählte Bilder und Tonaufnahmen.", - "draft-deletion": "Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.", + "draft-deletion": "Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetbrowser entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.", "settings-title": "6. Werbung und gespeicherte Einstellungen", "settings-data": "Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.", "children-title": "7. Inhalte von Schülerinnen und Schülern", @@ -492,7 +492,7 @@ globalThis.Wegwichtel ??= {}; "export-data": "Mit „Weg als Datei herunterladen“ kannst du den Weg in einer anderen Wander-App öffnen. Bilder und Tonaufnahmen sind in dieser Datei nicht enthalten.", "drafts-title": "Deine eigenen Strecken", "drafts-data": "Neue Strecken bleiben zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ reichst du eine Strecke zur Freigabe ein. Erst nach der Freigabe können andere sie sehen.", - "drafts-deletion": "Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.", + "drafts-deletion": "Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetbrowser entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.", "permissions-title": "Welche Erlaubnisse braucht die App?", "permissions": { "location": "Deinen Standort, um Strecken in deiner Nähe und den Weg zur nächsten Station zu zeigen.", diff --git a/test/app-route-control-runtime.test.js b/test/app-route-control-runtime.test.js index bfb91c5..0e280f4 100644 --- a/test/app-route-control-runtime.test.js +++ b/test/app-route-control-runtime.test.js @@ -322,7 +322,6 @@ async function loadApp({ language = 'de-DE' } = {}) { test('English browser settings localize app controls and retain user-authored trail names', async () => { const { context, harness } = await loadApp({ language: 'en-GB' }); await context.Wegwichtel.App.initialize({ markStep() {} }); - assert.equal(harness.node('#route-control-title').textValue, 'Choose a trail'); assert.equal(harness.node('#route-control-server-title').textValue, 'Public trails (1)'); assert.equal(harness.node('#route-control-local-title').textValue, 'My drafts (1)'); assert.match(harness.node('#location-hint').textValue, /You can still choose from all trails/); @@ -360,8 +359,7 @@ test('route control runtime loads server/local routes and supports switching wit localButton.dataValues.set('route-id', 'local-1'); await harness.activate('[data-route-select]', localButton); - assert.equal(harness.node('#route-control-title').textValue, 'Lokaler Weg'); - assert.match(harness.node('#route-control-next').textValue, /Nächste Station: Lokaler Weg Start/); + assert.equal(harness.node('#route-title').textValue, 'Lokaler Weg'); assert.equal(harness.node('#route-page').attributes.has('hidden'), false); const serverButton = new FakeNode('server-select'); @@ -370,8 +368,7 @@ test('route control runtime loads server/local routes and supports switching wit await harness.key('[data-route-select]', serverButton, 'Enter'); assert.ok(apiCalls.includes('route:server-1')); - assert.equal(harness.node('#route-control-title').textValue, 'Serverweg'); - assert.match(harness.node('#route-control-next').textValue, /Nächste Station: Serverweg Start/); + assert.equal(harness.node('#route-title').textValue, 'Serverweg'); }); test('local-route change refreshes an active draft and handles its later removal', async () => { @@ -391,7 +388,7 @@ test('local-route change refreshes an active draft and handles its later removal harness.document.dispatchEvent(updateEvent); await tick(); - assert.equal(harness.node('#route-control-title').textValue, 'Lokaler Weg geändert'); + assert.equal(harness.node('#route-title').textValue, 'Lokaler Weg geändert'); assert.equal(editor.refreshed, 1); setup.setLocalRoute(null); @@ -400,7 +397,6 @@ test('local-route change refreshes an active draft and handles its later removal harness.document.dispatchEvent(removeEvent); await tick(); - assert.equal(harness.node('#route-control-title').textValue, 'Strecke wählen'); assert.equal(harness.node('#routes-page').attributes.has('hidden'), false); assert.equal(editor.refreshed, 2); }); @@ -466,11 +462,9 @@ test('tracking triggers each POI once, records media progress and pauses/resumes await harness.activate('#start-route', new FakeNode('resume')); calls.position({ lat: 50.001, lon: 10.001, accuracy: 5 }); assert.equal(calls.vibrations, 2); - assert.equal(harness.node('#route-control-next').textValue, 'Alle Stationen erreicht'); assert.equal(harness.node('#navigation-distance').textValue, '0 m'); await harness.activate('#stop-route', new FakeNode('stop')); assert.equal(savedStates.at(-1)[1], 'idle'); - assert.equal(harness.node('#route-control-title').textValue, 'Strecke wählen'); assert.equal(harness.node('#navigation-footer').properties.get('hidden'), true); assert.ok(calls.stopped >= 4); }); diff --git a/test/legal-ui.test.js b/test/legal-ui.test.js index 9145e2f..e4ae9a9 100644 --- a/test/legal-ui.test.js +++ b/test/legal-ui.test.js @@ -79,6 +79,10 @@ class FakeElement extends EventTarget { this.dataset = {}; this.attributes = new Map(); this.focused = false; + this.classes = new Set(); + this.classList = { + toggle: (name, enabled) => enabled ? this.classes.add(name) : this.classes.delete(name) + }; } focus() { @@ -140,6 +144,10 @@ test('legal overlay handlers synchronize switches without advancing the start sc const cookieToggles = [new FakeElement()]; let enabled = false; const changes = []; + const frames = []; + function flushFrames() { + while (frames.length) frames.shift()(); + } class TestCustomEvent extends Event { constructor(type, options = {}) { @@ -178,7 +186,8 @@ test('legal overlay handlers synchronize switches without advancing the start sc document, Event, EventTarget, - CustomEvent: TestCustomEvent + CustomEvent: TestCustomEvent, + requestAnimationFrame: callback => frames.push(callback) }; context.globalThis = context; await loadI18n(context); @@ -188,6 +197,22 @@ test('legal overlay handlers synchronize switches without advancing the start sc assert.equal(elements.get('info-dropdown').hidden, false); assert.equal(elements.get('info-button').attributes.get('aria-expanded'), 'true'); assert.equal(elements.get('info-dialog').open, false); + assert.equal(elements.get('info-dropdown').classes.has('is-open'), false); + frames.shift()(); + assert.equal(elements.get('info-dropdown').classes.has('is-open'), false); + frames.shift()(); + assert.equal(elements.get('info-dropdown').classes.has('is-open'), true); + + elements.get('info-button').dispatchEvent(pointerEvent()); + assert.equal(elements.get('info-dropdown').classes.has('is-open'), false); + elements.get('info-button').dispatchEvent(pointerEvent()); + elements.get('info-button').dispatchEvent(pointerEvent()); + flushFrames(); + assert.equal(elements.get('info-dropdown').hidden, true); + assert.equal(elements.get('info-dropdown').classes.has('is-open'), false); + + elements.get('info-button').dispatchEvent(pointerEvent()); + flushFrames(); targetButtons[0].dispatchEvent(pointerEvent()); assert.equal(elements.get('info-dialog').open, true); diff --git a/test/route-selection-ui.test.js b/test/route-selection-ui.test.js index 0d73c6d..d4f66cb 100644 --- a/test/route-selection-ui.test.js +++ b/test/route-selection-ui.test.js @@ -18,8 +18,7 @@ test('main screen contains a persistent route control next to the information co assert.match(html, /class="route-control"/); assert.match(html, /id="route-control-button"/); assert.match(html, /id="route-control-dropdown"/); - assert.match(html, /id="route-control-title"/); - assert.match(html, /id="route-control-next"/); + assert.doesNotMatch(html, /route-control-summary|route-control-title|route-control-next/); assert.match(html, /id="route-control-server-list"/); assert.match(html, /id="route-control-local-list"/); assert.match(html, /id="route-control-new"/); @@ -52,12 +51,12 @@ test('route selection separates activated server routes from local browser route assert.match(app, /\[data-route-select\]/); }); -test('persistent route control displays the next unvisited Station and preserves progress when switching', async () => { +test('route switching preserves progress and navigation continues to target unvisited stations', async () => { const app = await read('public/js/app.js'); - assert.match(app, /function nextPoiMetric\(/); - assert.match(app, /I18n\.t\("app\.next-station-summary", \{ value1: metric\.poi\.title, value2: distance \}\)/); - assert.match(app, /state\.triggered\.has\(poi\.id\)/); + assert.match(app, /function nearestPendingPoi\(/); + assert.match(app, /function showPoiNavigation\(/); + assert.match(app, /state\.triggered\.has\(metric\.poi\.id\)/); assert.match(app, /function suspendCurrentRoute\(/); assert.match(app, /persistRouteProgress\(\)/); assert.match(app, /await selectRoute/);