// Runde-3-Tests: downloadBbox, rollendes Vorausladen (setGps), Cap 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); }, 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 = {}; let persistCalled = 0; Object.defineProperty(globalThis, 'navigator', { value: { onLine: true, storage: { persist: () => { persistCalled++; return Promise.resolve(true); } } }, configurable: true }); global.pmtiles = { PMTiles: class { getZxy() { return Promise.resolve({ data: new Uint8Array(100).buffer }); } } }; global.MapGLStyle = { tilesUrl: () => 'http://test/dach.pmtiles' }; global.fetch = () => Promise.resolve({ ok: true, arrayBuffer: () => Promise.resolve(new Uint8Array(50).buffer), json: () => Promise.resolve([]) }); eval(fs.readFileSync(process.argv[2], 'utf8')); const MO = global.window.MapOffline; (async () => { // 1. downloadBbox: kleiner Ausschnitt const r1 = await MO.downloadBbox({ south: 48.06, west: 11.94, north: 48.09, east: 11.99 }, { capMB: 40 }); console.log('downloadBbox:', JSON.stringify(r1)); if (!(r1.tiles > 0)) throw new Error('Bbox-Download leer'); if (!persistCalled) throw new Error('storage.persist() nicht aufgerufen'); // 2. Zu-groß-Schutz let threw = false; await MO.downloadBbox({ south: 40, west: 0, north: 55, east: 20 }, {}).catch(e => { threw = /zu groß/.test(e.message); }); console.log('Zu-groß-Schutz:', threw); if (!threw) throw new Error('Zu-groß-Schutz fehlt'); // 3. totalBytes-Zähler + stats const s = await MO.stats(); console.log('totalBytes:', s.totalBytes); if (!(s.totalBytes > 0)) throw new Error('totalBytes nicht gezählt'); // 4. Rollendes Vorausladen: setGps zweimal — erst > 400m Distanz lädt neu const before = stores.tiles.size; MO.setGps({ lat: 47.50, lon: 11.50 }); await new Promise(r => setTimeout(r, 300)); const afterFirst = stores.tiles.size; console.log('Prefetch nach 1. setGps:', afterFirst - before, 'neue Keys'); if (afterFirst <= before) throw new Error('Rollendes Vorausladen lädt nichts'); MO.setGps({ lat: 47.5001, lon: 11.5001 }); // < 400 m → kein neuer Load await new Promise(r => setTimeout(r, 200)); if (stores.tiles.size !== afterFirst) throw new Error('Distanz-Throttle greift nicht'); MO.setGps({ lat: 47.51, lon: 11.52 }); // > 400 m → neuer Ring await new Promise(r => setTimeout(r, 300)); console.log('Prefetch nach Bewegung:', stores.tiles.size - afterFirst, 'neue Keys'); if (stores.tiles.size <= afterFirst) throw new Error('Vorausladen nach Bewegung fehlt'); // 5. Cap stoppt Vorausladen stores.meta.set('totalBytes', 300 * 1048576); const capBefore = stores.tiles.size; MO.setGps({ lat: 47.60, lon: 11.60 }); await new Promise(r => setTimeout(r, 300)); if (stores.tiles.size !== capBefore) throw new Error('Cap stoppt Vorausladen nicht'); console.log('Cap-Guard: ok'); console.log('\nALLE RUNDE-3-TESTS BESTANDEN'); })().catch(e => { console.error('FEHLER:', e.message); process.exit(1); });