Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions public/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -2614,6 +2614,10 @@
topRoutesLayer = null;
topRoutesEdges = null;
if (map) {
// Leaflet 1.9.4 ends an animated zoom from a 250 ms setTimeout that
// remove() does not cancel; it then reads the removed map pane
// ("_leaflet_pos"). With the flag cleared that callback returns early.
map._animatingZoom = false;
map.remove();
map = null;
}
Expand Down
17 changes: 3 additions & 14 deletions public/path-inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,9 @@
function showOnMap(candidate) {
// Store pending route for map init to pick up.
window._pendingPathInspectorRoute = candidate;
// Switch to map page if not there; map init will draw the route.
if (location.hash.indexOf('#/map') !== 0) {
location.hash = '#/map';
} else {
// Already on map — draw directly.
delete window._pendingPathInspectorRoute;
if (window.routeLayer) window.routeLayer.clearLayers();
// Pass FULL path as hopKeys (not slice(1)) — drawPacketRoute resolves
// each entry against nodes[] for plotting. The 2nd arg is the origin
// OBJECT (with pubkey/lat/lon/name); pass null since the origin is
// already the first hop in the path itself, and drawPacketRoute draws
// a marker for every resolved hop.
if (window.drawPacketRoute) window.drawPacketRoute(candidate.path, null);
}
// This standalone page navigates to the map; its embedded inspector
// has a separate handler in map.js.
location.hash = '#/map';
}

function escapeAttr(s) {
Expand Down
26 changes: 26 additions & 0 deletions test-issue-123-map-lifecycle-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,32 @@ const settle = (page, ms) => page.waitForTimeout(ms || 400);
});
}

// #345 CI: Leaflet 1.9.4 ends an animated zoom from a 250 ms setTimeout that
// map.remove() does not cancel; leaving mid-zoom threw "_leaflet_pos" (e.g.
// right after a route's fitBounds).
await step('leaving during an animated zoom: Leaflet\'s transition-end timer is inert', async () => {
const { ctx, page } = await newPage(browser, errors);
const errsBefore = errors.length;
try {
await page.goto(BASE + '/#/map');
await mapLoaded(page);
const animating = await page.evaluate(() => new Promise((resolve) => {
const m = window.__mc_map;
m.setZoom(m.getZoom() + 1, { animate: true });
const t0 = Date.now();
(function poll() {
if (m._animatingZoom) { location.hash = '#/packets'; return resolve(true); }
if (Date.now() - t0 > 200) return resolve(false);
requestAnimationFrame(poll);
})();
}));
assert(animating, 'fixture: the zoom did not animate');
await page.waitForSelector('#pktTable', { state: 'attached' });
await settle(page, 800);
assert(errors.length === errsBefore, errors.slice(errsBefore).join(' | '));
} finally { await ctx.close(); }
});

// #123 review round: destroy() must drop the tile-provider listener and the
// theme MutationObserver of its mount. Counts only map.js's own callbacks
// (they call _syncDarkTiles; live.js has its own tile listener).
Expand Down
11 changes: 11 additions & 0 deletions test-path-inspector-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ async function findCandidatePair(request) {
assert(await page.evaluate(() => document.body.classList.contains('mc-route-active')), 'route view not active');
});

await step('standalone "Show on Map" navigates, consumes the handoff, and draws the route', async () => {
assert(pair, 'no candidate to show');
await page.goto(BASE + '/#/tools/path-inspector?prefixes=' + pair.prefixes, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#path-inspector-results button[data-idx="0"]');
await page.click('#path-inspector-results button[data-idx="0"]');
await page.waitForFunction(() => location.hash === '#/map');
await page.waitForSelector('#leaflet-map .leaflet-overlay-pane path.mc-rt-edge', { state: 'visible' });
assert(await page.evaluate(() => document.body.classList.contains('mc-route-active')), 'route view not active');
assert(await page.evaluate(() => !window._pendingPathInspectorRoute), 'pending route was not consumed');
});

await step('Tools landing links to the Path Inspector and to Trace', async () => {
await page.goto(BASE + '/#/tools', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.tools-landing', { state: 'visible' });
Expand Down
32 changes: 31 additions & 1 deletion test-path-inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,34 @@ function createSandbox() {
// Anti-tautology: if validation were removed (always return null), the odd-length test would fail.
// Mental revert: validatePrefixes = () => null; → testValidateOdd would fail because err would be null.

console.log('\nAll path-inspector tests passed!');
// The standalone inspector hands off to map init; map.js owns its embedded
// inspector and must not be reached through nonexistent window drawing APIs.
assert(!/window\.(?:drawPacketRoute|routeLayer)\b/.test(src),
'standalone inspector must not depend on private map drawing globals');
console.log('✓ no obsolete map drawing globals');

(async function testShowOnMapHandoff() {
const sb = createSandbox();
const candidate = {
path: ['aa'.repeat(32), 'bb'.repeat(32)],
names: ['Test A', 'Test B'], score: 1, evidence: { perHop: [] }
};
let click;
const button = { dataset: { idx: '0' }, addEventListener: (event, fn) => { click = fn; } };
const results = {
innerHTML: '',
querySelectorAll: selector => selector === 'button[data-idx]' ? [button] : []
};
sb.document.getElementById = id => id === 'path-inspector-results' ? results :
{ textContent: '', addEventListener: () => {} };
sb.fetch = async () => ({ ok: true, json: async () => ({ candidates: [candidate], stats: {} }) });
sb.location.hash = '#/tools/path-inspector?prefixes=aa,bb';
sb.window.PathInspector.init({ innerHTML: '' });
await new Promise(resolve => setImmediate(resolve));
assert.strictEqual(typeof click, 'function', 'candidate button must be wired');
click();
assert.strictEqual(sb.window._pendingPathInspectorRoute, candidate, 'preserve the full candidate');
assert.strictEqual(sb.location.hash, '#/map');
console.log('✓ candidate button hands the complete route to map init');
console.log('\nAll path-inspector tests passed!');
})().catch(error => { console.error(error); process.exitCode = 1; });
Loading