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
16 changes: 11 additions & 5 deletions public/packet-path-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,18 @@
// markers stay circleMarker dots throughout (a role-specific shape
// would clash with the color/dash coding already carrying primary,
// approx, and observer meaning).
// Phosphor sprite icons, not emoji (#1648 lint gate); same glyphs as
// observer-detail.js's directNeighborRoleIcon.
function phIcon(name) {
return '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-' + name + '"/></svg> ';
}

function roleIcon(role) {
switch (role) {
case 'repeater': return 'πŸ“‘ ';
case 'room': return '🏠 ';
case 'client': return 'πŸ“± ';
case 'sensor': return '🌑️ ';
case 'repeater': return phIcon('broadcast');
case 'room': return phIcon('house-line');
case 'client': return phIcon('radio');
case 'sensor': return phIcon('thermometer');
default: return '';
}
}
Expand Down Expand Up @@ -412,7 +418,7 @@
radius: 11, color: cssVar('--status-green'), weight: 3, fillOpacity: 0, opacity: 0.9,
})
.addTo(map)
.bindTooltip('🏁 First to hear it: ' + escapeHtml(firstPoint.name) + ' (' + data.first.hops + ' hop' + (data.first.hops === 1 ? '' : 's') + (firstPoint.approx ? ', approx. position' : '') + ')', { className: 'packet-path-tooltip' });
.bindTooltip(phIcon('flag') + 'First to hear it: ' + escapeHtml(firstPoint.name) + ' (' + data.first.hops + ' hop' + (data.first.hops === 1 ? '' : 's') + (firstPoint.approx ? ', approx. position' : '') + ')', { className: 'packet-path-tooltip' });
}

try { map.fitBounds(bounds, { padding: [30, 30] }); } catch (e) { /* single point */ }
Expand Down
17 changes: 14 additions & 3 deletions test-issue-1485-live-anim-z.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,26 @@ console.log('\n=== #1485 live anim z-order C: per-shape inheritance ===');

// At least one polyline/circleMarker addTo(animLayer) call exists β€” we're
// trusting Leaflet pane inheritance from the LayerGroup parent. Sanity
// check that the four animLayer / pathsLayer addTo sites still exist
// check that the animLayer / pathsLayer addTo sites still exist
// (regression detector if someone moves circles to the default pane).
// animLayer hosts 2 since #1521 (ghost markers + matrix char markers):
// the pulseNode() rings moved off animLayer into the canvas engine,
// whose pane is checked below.
const animAddTo = (liveSrc.match(/\.addTo\(animLayer\)/g) || []).length;
const pathsAddTo = (liveSrc.match(/\.addTo\(pathsLayer\)/g) || []).length;
assert(animAddTo >= 3,
'animLayer still hosts >=3 .addTo() animation shapes (got ' + animAddTo + ')');
assert(animAddTo >= 2,
'animLayer still hosts >=2 .addTo() animation shapes (got ' + animAddTo + ')');
assert(pathsAddTo >= 3,
'pathsLayer still hosts >=3 .addTo() trail shapes (got ' + pathsAddTo + ')');

// The canvas engine (in-flight packets + pulse rings since #1521) draws
// on 'animationsPane', which must also sit above markerPane (600).
const canvasZ = liveSrc.match(
/getPane\(\s*['"]animationsPane['"]\s*\)\.style\.zIndex\s*=\s*['"]?(\d+)/
);
assert(!!canvasZ && parseInt(canvasZ[1], 10) > 600,
'animationsPane (canvas pulses) z-index > markerPane 600 (got ' + (canvasZ ? canvasZ[1] : 'none') + ')');

console.log('\n=== Summary ===');
console.log(' Passed: ' + passed);
console.log(' Failed: ' + failed);
Expand Down
22 changes: 15 additions & 7 deletions test-live-anims.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,24 @@ test2('drawAnimatedLine renderFades() has null guard', () => {
assert.ok(fadeBody.includes('!pathsLayer'), 'renderFades() missing pathsLayer null guard');
});

test2('pulseNode animatePulse() has null guard', () => {
const pulseStart = src2.indexOf('function animatePulse(now)');
// #1521 replaced the per-node animatePulse()/ghostPulse() rAF loops with
// the activePulses/activeGhosts queues drained by renderAnimations(); the
// guards now live at pulseNode() entry and in renderAnimations().
test2('pulseNode() has null guard', () => {
const pulseStart = src2.indexOf('function pulseNode(');
assert.ok(pulseStart >= 0, 'pulseNode() not found');
const pulseBody = src2.substring(pulseStart, pulseStart + 200);
assert.ok(pulseBody.includes('!animLayer'), 'animatePulse() missing animLayer null guard');
assert.ok(pulseBody.includes('!animLayer || !nodesLayer'), 'pulseNode() missing animLayer/nodesLayer null guard');
});

test2('ghostPulse has null guard', () => {
const ghostStart = src2.indexOf('function ghostPulse(now)');
const ghostBody = src2.substring(ghostStart, ghostStart + 200);
assert.ok(ghostBody.includes('!animLayer'), 'ghostPulse() missing animLayer null guard');
test2('renderAnimations() has null guards for canvas and ghost expiry', () => {
const renderStart = src2.indexOf('function renderAnimations(now)');
assert.ok(renderStart >= 0, 'renderAnimations() not found');
const renderEnd = src2.indexOf('// Render Pulses', renderStart);
assert.ok(renderEnd > renderStart, 'renderAnimations() pulse section not found');
const renderBody = src2.substring(renderStart, renderEnd);
assert.ok(renderBody.includes('if (!animCtx) return;'), 'renderAnimations() missing animCtx null guard');
assert.ok(renderBody.includes('animLayer && animLayer.hasLayer(g.marker)'), 'renderAnimations() ghost expiry missing animLayer null guard');
});

console.log(`\n${p2} passed, ${f2} failed\n`);
Expand Down
2 changes: 1 addition & 1 deletion test-naive-banner-tone.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function test(name, fn) {
// Load observer-detail.js in a VM sandbox
const src = fs.readFileSync(__dirname + '/public/observer-detail.js', 'utf8');
const window = {};
const ctx = vm.createContext({ window, document: { getElementById: () => null, querySelector: () => null, querySelectorAll: () => [], createElement: () => ({ style: {}, classList: { add(){}, remove(){} }, appendChild(){}, setAttribute(){} }), createDocumentFragment: () => ({ appendChild(){} }) }, console, setTimeout, setInterval, clearInterval, fetch: () => Promise.resolve({ ok: true, json: () => Promise.resolve({}) }), location: { hash: '' }, history: { replaceState() {} }, Chart: function() { return { destroy(){}, update(){} }; }, registerPage() {}, L: { map(){ return { setView(){ return this; }, on(){ return this; }, remove(){} }; }, tileLayer(){ return { addTo(){} }; } } });
const ctx = vm.createContext({ window, document: { addEventListener() {}, getElementById: () => null, querySelector: () => null, querySelectorAll: () => [], createElement: () => ({ style: {}, classList: { add(){}, remove(){} }, appendChild(){}, setAttribute(){} }), createDocumentFragment: () => ({ appendChild(){} }) }, console, setTimeout, setInterval, clearInterval, fetch: () => Promise.resolve({ ok: true, json: () => Promise.resolve({}) }), location: { hash: '' }, history: { replaceState() {} }, Chart: function() { return { destroy(){}, update(){} }; }, registerPage() {}, L: { map(){ return { setView(){ return this; }, on(){ return this; }, remove(){} }; }, tileLayer(){ return { addTo(){} }; } } });
vm.runInContext(src, ctx);

const render = ctx.window.ObserverDetailNaiveBanner.render;
Expand Down
2 changes: 1 addition & 1 deletion test-observers-headings.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ test('expected headings present and ordered', () => {
while ((m = re.exec(thead)) !== null) labels.push(m[1].trim());
const expected = ['Status', 'Name', 'Region', 'Last Status', 'Last Packet',
'Packet Health', 'Total Packets', 'Packets/Hour', 'Clock Offset', 'Uptime',
'Firmware', 'Client'];
'Firmware', 'Client', 'Neighbors'];
assert.deepStrictEqual(labels, expected,
`Headings out of sync.\nGot: ${JSON.stringify(labels)}\nExpected: ${JSON.stringify(expected)}`);
});
Expand Down
4 changes: 2 additions & 2 deletions test-packet-path-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -1042,8 +1042,8 @@ function makeSandbox(apiImpl) {
};

await ctx.window.PacketPathMap.open('deadbeef');
assert.ok(tooltips.some((t) => t.includes('πŸ“‘') && t.includes('RepeaterA')), 'expected a repeater icon on RepeaterA, got: ' + JSON.stringify(tooltips));
assert.ok(tooltips.some((t) => t.includes('🏠') && t.includes('RoomObserver')), 'expected a room icon on RoomObserver, got: ' + JSON.stringify(tooltips));
assert.ok(tooltips.some((t) => t.includes('#ph-broadcast') && t.includes('RepeaterA')), 'expected a repeater icon on RepeaterA, got: ' + JSON.stringify(tooltips));
assert.ok(tooltips.some((t) => t.includes('#ph-house-line') && t.includes('RoomObserver')), 'expected a room icon on RoomObserver, got: ' + JSON.stringify(tooltips));
passed++;
console.log(' βœ… nodes with a known role get a role icon in their tooltip');
} catch (e) { failed++; console.log(' ❌ nodes with a known role get a role icon in their tooltip: ' + e.message); }
Expand Down
Loading