Offline-Karten Runde 7: 'Alles loeschen' selektiv — Standort + Routen-Korridore bleiben
Idee Rene (spart Vorladezeit + Daten): statt loeschen-und-neu-laden bleiben
- Standort-Gebiete (Regionen type 'standort')
- Korridore der gespeicherten Routen (clear({keepTracks}) aus preview_track)
- 5-km-Umkreis der aktuellen Position + Basis-Zooms 0-9
- Marker/Warnungen (p/) + Glyphs (f/)
Geloescht: manuelle Gebiete/Ausschnitte + Funkloch-Kacheln (Zonen bleiben
gemerkt, Nahe laden automatisch neu). Ohne Keep-Kandidaten: Komplett-Wipe.
Batch-Delete in einer Transaktion. Tests r7 neu, r6 angepasst, Regression gruen.
Bump v1235
This commit is contained in:
parent
94a6ce49ba
commit
29cd489287
11 changed files with 193 additions and 39 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1234
|
1235
|
||||||
|
|
@ -86,14 +86,14 @@
|
||||||
<title>Ban Yaro</title>
|
<title>Ban Yaro</title>
|
||||||
|
|
||||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||||
<script src="/js/boot-early.js?v=1234"></script>
|
<script src="/js/boot-early.js?v=1235"></script>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css?v=1234">
|
<link rel="stylesheet" href="/css/design-system.css?v=1235">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=1234">
|
<link rel="stylesheet" href="/css/layout.css?v=1235">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=1234">
|
<link rel="stylesheet" href="/css/components.css?v=1235">
|
||||||
<link rel="stylesheet" href="/css/utilities.css?v=1234">
|
<link rel="stylesheet" href="/css/utilities.css?v=1235">
|
||||||
<link rel="stylesheet" href="/css/lists.css?v=1234">
|
<link rel="stylesheet" href="/css/lists.css?v=1235">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -612,11 +612,11 @@
|
||||||
<div id="modal-container"></div>
|
<div id="modal-container"></div>
|
||||||
|
|
||||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||||
<script src="/js/api.js?v=1234"></script>
|
<script src="/js/api.js?v=1235"></script>
|
||||||
<script src="/js/ui.js?v=1234"></script>
|
<script src="/js/ui.js?v=1235"></script>
|
||||||
<script src="/js/app.js?v=1234"></script>
|
<script src="/js/app.js?v=1235"></script>
|
||||||
<script src="/js/worlds.js?v=1234"></script>
|
<script src="/js/worlds.js?v=1235"></script>
|
||||||
<script src="/js/offline-indicator.js?v=1234"></script>
|
<script src="/js/offline-indicator.js?v=1235"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
@ -626,7 +626,7 @@
|
||||||
|
|
||||||
|
|
||||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||||
<script src="/js/boot.js?v=1234"></script>
|
<script src="/js/boot.js?v=1235"></script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
Router, State-Management, Navigation, Initialisierung.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const APP_VER = '1234'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '1235'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||||
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
||||||
window.APP_VERSION = APP_VERSION;
|
window.APP_VERSION = APP_VERSION;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,16 @@ window.MapOffline = (function () {
|
||||||
var _put = function (k, v) { return _req(STORE, 'readwrite', function (os) { os.put(v, k); }); };
|
var _put = function (k, v) { return _req(STORE, 'readwrite', function (os) { os.put(v, k); }); };
|
||||||
var _del = function (k) { return _req(STORE, 'readwrite', function (os) { os.delete(k); }); };
|
var _del = function (k) { return _req(STORE, 'readwrite', function (os) { os.delete(k); }); };
|
||||||
var _count = function () { return _req(STORE, 'readonly', function (os) { return os.count(); }); };
|
var _count = function () { return _req(STORE, 'readonly', function (os) { return os.count(); }); };
|
||||||
|
// Viele Keys in EINER Transaktion löschen (einzelne _del-Transaktionen wären zu langsam).
|
||||||
|
function _delMany(keys) {
|
||||||
|
if (!keys.length) return Promise.resolve();
|
||||||
|
return _open().then(function (d) { return new Promise(function (res, rej) {
|
||||||
|
var tx = d.transaction(STORE, 'readwrite'), os = tx.objectStore(STORE);
|
||||||
|
keys.forEach(function (k) { os.delete(k); });
|
||||||
|
tx.oncomplete = function () { res(); };
|
||||||
|
tx.onerror = function () { rej(tx.error); };
|
||||||
|
}); });
|
||||||
|
}
|
||||||
var _metaGet = function (k) { return _req(META, 'readonly', function (os) { return os.get(k); }); };
|
var _metaGet = function (k) { return _req(META, 'readonly', function (os) { return os.get(k); }); };
|
||||||
var _metaPut = function (k, v) { return _req(META, 'readwrite', function (os) { os.put(v, k); }); };
|
var _metaPut = function (k, v) { return _req(META, 'readwrite', function (os) { os.put(v, k); }); };
|
||||||
|
|
||||||
|
|
@ -750,18 +760,76 @@ window.MapOffline = (function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function hasRegion() { return stats().then(function (s) { return s.count > 0; }).catch(function () { return false; }); }
|
function hasRegion() { return stats().then(function (s) { return s.count > 0; }).catch(function () { return false; }); }
|
||||||
// „Alles löschen" entfernt Kacheln/Marker/Regionen — das FUNKLOCH-GEDÄCHTNIS bleibt
|
// Kachel-Keys eines Umkreises (alle Zooms) ins Keep-Set legen.
|
||||||
// (Quelle der Wahrheit, Modell René 2026-06-08): Zonen werden auf filled:false gesetzt
|
function _keepRegionKeys(lat, lon, radiusKm, keep) {
|
||||||
// und beim nächsten Online-Start in Positionsnähe automatisch neu geladen.
|
var bb = _bboxAround(lat, lon, radiusKm);
|
||||||
function clear() {
|
for (var z = 10; z <= MAXZOOM; z++) {
|
||||||
return _req(STORE, 'readwrite', function (os) { os.clear(); })
|
var x0 = _x(bb.west, z), x1 = _x(bb.east, z), y0 = _y(bb.north, z), y1 = _y(bb.south, z);
|
||||||
.then(function () { return _metaGet('deadzones'); })
|
for (var x = x0; x <= x1; x++) for (var y = y0; y <= y1; y++) keep[z + '/' + x + '/' + y] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Korridor-Keys eines Tracks (±bufferKm, z10–14) ins Keep-Set legen.
|
||||||
|
function _keepCorridorKeys(track, bufferKm, keep) {
|
||||||
|
track.forEach(function (p) {
|
||||||
|
var d = Math.ceil(bufferKm / _tileKm(MAXZOOM, p.lat));
|
||||||
|
var cx = _x(p.lon, MAXZOOM), cy = _y(p.lat, MAXZOOM);
|
||||||
|
for (var x = cx - d; x <= cx + d; x++) for (var y = cy - d; y <= cy + d; y++) {
|
||||||
|
keep[MAXZOOM + '/' + x + '/' + y] = 1;
|
||||||
|
for (var pz = 13; pz >= 10; pz--) {
|
||||||
|
keep[pz + '/' + (x >> (MAXZOOM - pz)) + '/' + (y >> (MAXZOOM - pz))] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// „Alles löschen" — SELEKTIV (René 2026-06-08, spart Vorladezeit):
|
||||||
|
// BLEIBEN: Standort-Gebiete (Regionen type 'standort'), Korridore der übergebenen
|
||||||
|
// Routen-Tracks (opts.keepTracks), der Umkreis von opts.center (5 km), Basis-Zooms 0–9
|
||||||
|
// (winzig, von allem gebraucht), Marker/Warnungen ('p/') + Glyphs ('f/').
|
||||||
|
// GEHEN: manuelle Gebiete/Ausschnitte + Funkloch-Kacheln (Zonen bleiben gemerkt,
|
||||||
|
// filled:false → Start-Check lädt Nahe automatisch neu).
|
||||||
|
// Ohne Keep-Kandidaten (alte Signatur/Tests): kompletter Wipe inkl. Basis-Zooms.
|
||||||
|
function clear(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
var keep = {};
|
||||||
|
return _metaGet('regions').then(function (regions) {
|
||||||
|
regions = regions || [];
|
||||||
|
var keptRegions = regions.filter(function (r) { return r.type === 'standort' || r.type === 'korridor'; });
|
||||||
|
regions.forEach(function (r) {
|
||||||
|
if (r.type === 'standort' && r.radiusKm) _keepRegionKeys(r.lat, r.lon, r.radiusKm, keep);
|
||||||
|
});
|
||||||
|
if (opts.center) _keepRegionKeys(opts.center.lat, opts.center.lon, 5, keep);
|
||||||
|
(opts.keepTracks || []).forEach(function (t) {
|
||||||
|
if (t && t.length >= 2) _keepCorridorKeys(t, 1, keep);
|
||||||
|
});
|
||||||
|
var keepBase = Object.keys(keep).length > 0;
|
||||||
|
if (!keepBase) keptRegions = []; // nichts zu behalten → echter Komplett-Wipe
|
||||||
|
|
||||||
|
return _req(STORE, 'readonly', function (os) { return os.getAllKeys(); }).then(function (keys) {
|
||||||
|
// Komplett-Wipe (nichts zu behalten): alles inkl. Marker/Glyphs (altes Verhalten).
|
||||||
|
if (!keepBase) return _delMany((keys || []).slice());
|
||||||
|
var doomed = (keys || []).filter(function (k) {
|
||||||
|
var m = /^(\d+)\//.exec(k);
|
||||||
|
if (!m) return false; // 'p/' + 'f/' bleiben
|
||||||
|
if (+m[1] <= 9) return false; // Basis-Zooms behalten
|
||||||
|
return !keep[k];
|
||||||
|
});
|
||||||
|
return _delMany(doomed);
|
||||||
|
}).then(function () { return _metaGet('deadzones'); })
|
||||||
.then(function (zones) {
|
.then(function (zones) {
|
||||||
return _req(META, 'readwrite', function (os) { os.clear(); }).then(function () {
|
return _req(META, 'readwrite', function (os) { os.clear(); }).then(function () {
|
||||||
|
var jobs = [];
|
||||||
if (zones && zones.length) {
|
if (zones && zones.length) {
|
||||||
zones.forEach(function (z) { z.filled = false; });
|
zones.forEach(function (z) { z.filled = false; });
|
||||||
return _metaPut('deadzones', zones);
|
jobs.push(_metaPut('deadzones', zones));
|
||||||
}
|
}
|
||||||
|
if (keptRegions.length) {
|
||||||
|
jobs.push(_metaPut('regions', keptRegions));
|
||||||
|
jobs.push(_metaPut('region', keptRegions[keptRegions.length - 1]));
|
||||||
|
jobs.push(_metaPut('totalBytes', keptRegions.reduce(function (a, r) { return a + (r.bytes || 0); }, 0)));
|
||||||
|
}
|
||||||
|
return Promise.all(jobs);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2308,13 +2308,22 @@ window.Page_map = (() => {
|
||||||
btn.innerHTML = `${UI.icon('trash')} Wirklich alles löschen?`;
|
btn.innerHTML = `${UI.icon('trash')} Wirklich alles löschen?`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await MapOffline.clear().catch(() => {});
|
// SELEKTIV löschen (René 2026-06-08, spart Vorladezeit): Standort-Gebiet + Korridore
|
||||||
|
// der gespeicherten Routen bleiben einfach stehen statt löschen-und-neu-laden.
|
||||||
|
let keepTracks = [];
|
||||||
|
try {
|
||||||
|
keepTracks = ((await API.routes.list()) || [])
|
||||||
|
.map(r => r.preview_track).filter(t => (t || []).length >= 2);
|
||||||
|
} catch (e) {}
|
||||||
|
await MapOffline.clear({
|
||||||
|
center: _userPos ? { lat: _userPos.lat, lon: _userPos.lon } : null,
|
||||||
|
keepTracks,
|
||||||
|
}).catch(() => {});
|
||||||
_setCoverage(false);
|
_setCoverage(false);
|
||||||
UI.modal.close();
|
UI.modal.close();
|
||||||
UI.toast.success('Offline-Karten gelöscht. Funkloch-Gebiete werden beim nächsten Start automatisch neu geladen.');
|
UI.toast.success('Offline-Karten gelöscht — Standort-Gebiet und Routen-Korridore bleiben erhalten.');
|
||||||
// Standort-Grundversorgung sofort wiederherstellen (René 2026-06-08: das Gebiet am
|
// Sicherheitsnetz: falls am Standort nichts zu behalten war (z.B. nie geladen),
|
||||||
// aktuellen Standort muss bleiben — es würde sonst nicht automatisch vorgeladen
|
// Grundversorgung jetzt herstellen.
|
||||||
// und die Offline-Funktionalität wäre genau hier weg).
|
|
||||||
if (_userPos && navigator.onLine) {
|
if (_userPos && navigator.onLine) {
|
||||||
try {
|
try {
|
||||||
const r = await MapOffline.ensureHomeArea(_userPos.lat, _userPos.lon);
|
const r = await MapOffline.ensureHomeArea(_userPos.lat, _userPos.lon);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<script src="/js/landing-init.js?v=1234"></script>
|
<script src="/js/landing-init.js?v=1235"></script>
|
||||||
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
||||||
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
|
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
|
||||||
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||||
const VER = '1234';
|
const VER = '1235';
|
||||||
const CACHE_VERSION = `by-v${VER}`;
|
const CACHE_VERSION = `by-v${VER}`;
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,15 @@ type 'standort', Cap-gated). Greift: (a) im Start-Check (raw IDB-Check ohne GL-S
|
||||||
Standort-Kachel da UND Zonen im 50-km-Umkreis gefüllt (ferne Zonen zählen nicht mehr — sie
|
Standort-Kachel da UND Zonen im 50-km-Umkreis gefüllt (ferne Zonen zählen nicht mehr — sie
|
||||||
laden erst vor Ort). Tests: tests/js/test-map-offline-r6.js.
|
laden erst vor Ort). Tests: tests/js/test-map-offline-r6.js.
|
||||||
|
|
||||||
|
**✅ Runde 7 — selektives Löschen (2026-06-08, Idee René: Vorladezeit sparen):**
|
||||||
|
„Alles löschen" löscht nicht mehr alles-und-lädt-neu, sondern **behält** Standort-Gebiete
|
||||||
|
(`type 'standort'` aus der Regions-Meta), die **Korridore der gespeicherten Routen**
|
||||||
|
(`clear({keepTracks})`, Tracks via API.routes.list/preview_track), den 5-km-Umkreis der
|
||||||
|
aktuellen Position, Basis-Zooms 0–9 sowie Marker/Warnungen + Glyphs. Gelöscht werden manuelle
|
||||||
|
Gebiete/Ausschnitte + Funkloch-Kacheln (Zonen bleiben gemerkt → Nahe laden automatisch neu).
|
||||||
|
Ohne Keep-Kandidaten: Komplett-Wipe wie bisher. Batch-Delete in einer Transaktion.
|
||||||
|
Tests: r7 (+ r6 angepasst).
|
||||||
|
|
||||||
**🔲 Offen (Backlog):**
|
**🔲 Offen (Backlog):**
|
||||||
- Echte LRU-Eviction (Refcounting/Region-Zuordnung der Kacheln), wenn Nutzer real ans Cap kommen.
|
- Echte LRU-Eviction (Refcounting/Region-Zuordnung der Kacheln), wenn Nutzer real ans Cap kommen.
|
||||||
- Rechteck-Zeichnen als präzisere Bereichsauswahl (Viewport-Variante deckt den Hauptfall ab).
|
- Rechteck-Zeichnen als präzisere Bereichsauswahl (Viewport-Variante deckt den Hauptfall ab).
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ for f in tests/js/test-map-offline*.js; do node "$f" backend/static/js/map-offli
|
||||||
- r4: Minimal-Speicher-Modell (Prune, Netz-Probe, clear behält Zonen, Nähe/Verify, Färbung)
|
- r4: Minimal-Speicher-Modell (Prune, Netz-Probe, clear behält Zonen, Nähe/Verify, Färbung)
|
||||||
- r5: Bbox-Replace (aufgehobene Warnungen), 24h-Alert-Refresh, removeDeadZone, ensureRouteCorridors
|
- r5: Bbox-Replace (aufgehobene Warnungen), 24h-Alert-Refresh, removeDeadZone, ensureRouteCorridors
|
||||||
- r6: Standort-Grundversorgung (ensureHomeArea: lädt/skippt/Cap, überlebt clear)
|
- r6: Standort-Grundversorgung (ensureHomeArea: lädt/skippt/Cap, überlebt clear)
|
||||||
|
- r7: selektives Löschen (Korridor-Keep via keepTracks, manuelle Gebiete weg, Komplett-Wipe-Fallback)
|
||||||
|
|
||||||
⚠️ Node 21+: eingebautes `navigator`-Global — Stubs via `Object.defineProperty(globalThis, 'navigator', …)`,
|
⚠️ Node 21+: eingebautes `navigator`-Global — Stubs via `Object.defineProperty(globalThis, 'navigator', …)`,
|
||||||
ein einfaches `global.navigator =` wird still verschluckt.
|
ein einfaches `global.navigator =` wird still verschluckt.
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,15 @@ const MO = global.window.MapOffline;
|
||||||
console.log('ensureHomeArea (vorhanden):', r2);
|
console.log('ensureHomeArea (vorhanden):', r2);
|
||||||
if (r2 !== 0 || stores.tiles.size !== before) throw new Error('Doppel-Download trotz Bestand');
|
if (r2 !== 0 || stores.tiles.size !== before) throw new Error('Doppel-Download trotz Bestand');
|
||||||
|
|
||||||
// 3. clear() → Zonen bleiben, Standort weg → ensureHomeArea lädt neu
|
// 3. clear() SELEKTIV: standort-Region bleibt stehen, Zonen bleiben gemerkt
|
||||||
await MO.markDeadZone(48.07, 11.96);
|
await MO.markDeadZone(48.07, 11.96);
|
||||||
|
const beforeClear = stores.tiles.size;
|
||||||
await MO.clear();
|
await MO.clear();
|
||||||
if (stores.tiles.size !== 0) throw new Error('clear unvollständig');
|
console.log('Nach clear: tiles', beforeClear, '→', stores.tiles.size, '— Zonen:', (stores.meta.get('deadzones') || []).length);
|
||||||
const r3 = await MO.ensureHomeArea(48.07, 11.96);
|
if (stores.tiles.size === 0) throw new Error('Standort-Gebiet überlebte clear nicht');
|
||||||
console.log('Nach clear neu geladen:', r3, '— Zonen erhalten:', (stores.meta.get('deadzones') || []).length);
|
|
||||||
if (r3 !== 1) throw new Error('Reload nach clear fehlt');
|
|
||||||
if ((stores.meta.get('deadzones') || []).length !== 1) throw new Error('Zonen weg');
|
if ((stores.meta.get('deadzones') || []).length !== 1) throw new Error('Zonen weg');
|
||||||
|
const r3 = await MO.ensureHomeArea(48.07, 11.96);
|
||||||
|
if (r3 !== 0) throw new Error('Standort hätte NICHT neu geladen werden müssen');
|
||||||
|
|
||||||
// 4. Über Cap → Auto-Pfad lädt nicht
|
// 4. Über Cap → Auto-Pfad lädt nicht
|
||||||
stores.meta.set('totalBytes', 300 * 1048576);
|
stores.meta.set('totalBytes', 300 * 1048576);
|
||||||
|
|
|
||||||
66
tests/js/test-map-offline-r7.js
Normal file
66
tests/js/test-map-offline-r7.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Runde-7-Tests: selektives Löschen (Korridor-Keep, manuelles Gebiet weg, Komplett-Wipe)
|
||||||
|
const fs = require('fs');
|
||||||
|
const stores = { tiles: new Map(), meta: new Map() };
|
||||||
|
function mkReq(result) { return { result }; }
|
||||||
|
global.indexedDB = { open() {
|
||||||
|
const req = {};
|
||||||
|
setTimeout(() => {
|
||||||
|
const db = {
|
||||||
|
objectStoreNames: { contains: n => !!stores[n] },
|
||||||
|
transaction(name) {
|
||||||
|
const os = {
|
||||||
|
get: k => mkReq(stores[name].get(k)),
|
||||||
|
put: (v, k) => { stores[name].set(k, v); return mkReq(undefined); },
|
||||||
|
delete: k => { stores[name].delete(k); return mkReq(undefined); },
|
||||||
|
clear: () => { stores[name].clear(); return mkReq(undefined); },
|
||||||
|
count: () => mkReq(stores[name].size),
|
||||||
|
getAllKeys: () => mkReq([...stores[name].keys()]),
|
||||||
|
};
|
||||||
|
const tx = { objectStore: () => os };
|
||||||
|
setTimeout(() => tx.oncomplete && tx.oncomplete());
|
||||||
|
return tx;
|
||||||
|
},
|
||||||
|
close() {},
|
||||||
|
};
|
||||||
|
req.result = db; req.onsuccess && req.onsuccess();
|
||||||
|
});
|
||||||
|
return req;
|
||||||
|
} };
|
||||||
|
global.window = {};
|
||||||
|
Object.defineProperty(globalThis, 'navigator', { value: { onLine: true, storage: { persist: () => Promise.resolve(true) } }, configurable: true });
|
||||||
|
global.pmtiles = { PMTiles: class { getZxy() { return Promise.resolve({ data: new Uint8Array(100).buffer }); } } };
|
||||||
|
global.MapGLStyle = { tilesUrl: () => 'http://t/d.pmtiles' };
|
||||||
|
global.fetch = () => Promise.resolve({ ok: true, arrayBuffer: () => Promise.resolve(new Uint8Array(50).buffer), json: () => Promise.resolve([{ id: 1, lat: 48.2, lon: 12.1 }]) });
|
||||||
|
eval(fs.readFileSync(process.argv[2], 'utf8'));
|
||||||
|
const MO = global.window.MapOffline;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Setup: manuelles Gebiet weit weg (lon 8) + Routen-Korridor (lon 12.1)
|
||||||
|
await MO.downloadAround(50.0, 8.0, { budgetMB: 0.005 });
|
||||||
|
const track = [{ lat: 48.20, lon: 12.10 }, { lat: 48.21, lon: 12.12 }, { lat: 48.22, lon: 12.14 }];
|
||||||
|
await MO.downloadCorridor(track, { bufferKm: 1, name: 'R' });
|
||||||
|
const before = stores.tiles.size;
|
||||||
|
const poisBefore = [...stores.tiles.keys()].filter(k => k.startsWith('p/')).length;
|
||||||
|
|
||||||
|
// 1. Selektiv: Korridor bleibt, manuelles Gebiet verschwindet, Marker bleiben
|
||||||
|
await MO.clear({ keepTracks: [track] });
|
||||||
|
const gj = await MO.coverage();
|
||||||
|
const lons = gj.features.map(f => f.geometry.coordinates[0][0][0]);
|
||||||
|
console.log('Selektiv: tiles', before, '→', stores.tiles.size, '— Coverage-Features:', gj.features.length);
|
||||||
|
if (!gj.features.length) throw new Error('Korridor überlebte nicht');
|
||||||
|
if (lons.some(l => l < 11.5)) throw new Error('Manuelles Gebiet (lon 8) überlebte');
|
||||||
|
const poisAfter = [...stores.tiles.keys()].filter(k => k.startsWith('p/')).length;
|
||||||
|
if (poisAfter !== poisBefore) throw new Error('Marker-Stores überlebten nicht');
|
||||||
|
const regs = stores.meta.get('regions') || [];
|
||||||
|
if (!regs.length || regs.some(r => r.type === 'gebiet')) throw new Error('Regions-Meta falsch gefiltert');
|
||||||
|
|
||||||
|
// 2. Komplett-Wipe ohne Keep-Kandidaten: alles weg (auch p/ + f/), Zonen bleiben
|
||||||
|
await MO.markDeadZone(48.2, 12.1);
|
||||||
|
await MO.clear();
|
||||||
|
console.log('Komplett-Wipe: tiles =', stores.tiles.size, '— Zonen:', (stores.meta.get('deadzones') || []).length);
|
||||||
|
// Korridor-Region hat keinen Track in der Meta → kein Keep-Set → echter Wipe
|
||||||
|
if (stores.tiles.size !== 0) throw new Error('Komplett-Wipe unvollständig');
|
||||||
|
if ((stores.meta.get('deadzones') || []).length !== 1) throw new Error('Zonen weg');
|
||||||
|
|
||||||
|
console.log('\nALLE RUNDE-7-TESTS BESTANDEN');
|
||||||
|
})().catch(e => { console.error('FEHLER:', e.message); process.exit(1); });
|
||||||
Loading…
Add table
Add a link
Reference in a new issue