mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-25 23:53:44 +00:00
Moves 290 root test-*.js into tests/unit (177, listed in test-all.sh) and tests/e2e (113, classified in scripts/non-unit-tests.json), per #1981 and PR-D of #1385. Root goes from 348 entries to 48. test-all.sh and test-fixtures/ stay put. The inventory guard now fails if a test reappears in the root or sits in the wrong folder.
Verified independently of the diff: the invoked sets are unchanged (test-all.sh 177 before and after, deploy.yml 96 before and after, both identical as sets), and a full local run of test-all.sh on master and on the branch produced 4702 output lines each whose only differences are absolute paths, stack-trace line numbers shifted by the REPO_ROOT line, the inventory wording and two perf ratios. The guard was mutation-checked: a test back in the root, a unit suite in tests/e2e, and a suite dropped from test-all.sh each make it exit 1. CI run 35246304316 ran 97 suites from tests/e2e and is green.
Follow-up 9335c51d finished the instruction files: no bare root test command is left in AGENTS.md, the squad charters, .github or docs, and every tests/ path they name resolves.
Merged by the interim maintainer without a second human reviewer: CI and the local runs above are the independent checks.
Known and deliberately out of scope: 18 of the 113 files in tests/e2e are invoked by no runner at all, and one of them cannot run anywhere because it requires jsdom, which is not a declared dependency. Tracked separately.
86 lines
3.9 KiB
JavaScript
86 lines
3.9 KiB
JavaScript
'use strict';
|
|
const REPO_ROOT = require('path').resolve(__dirname, '..', '..');
|
|
// Issue #1812: hop count at this node, node analytics page. Loads the browser
|
|
// IIFE in a vm sandbox (pattern from test-node-reach-coverage.js) and exercises
|
|
// the filter, histogram, box statistics and section render.
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
|
|
const code = fs.readFileSync(path.join(REPO_ROOT, 'public', 'node-hop-analytics.js'), 'utf8');
|
|
const sandbox = { window: {}, console };
|
|
vm.createContext(sandbox);
|
|
vm.runInContext(code, sandbox);
|
|
const H = sandbox.window.NodeHopAnalytics;
|
|
|
|
let passed = 0;
|
|
function test(name, fn) {
|
|
fn();
|
|
passed++;
|
|
console.log(' ok ' + name);
|
|
}
|
|
|
|
const packets = [
|
|
{ hash: 'a', hops: 0, tags: ['flood', 'unscoped'] },
|
|
{ hash: 'b', hops: 2, tags: ['flood', 'scoped'] },
|
|
{ hash: 'c', hops: 5, tags: ['flood', 'unscoped', 'advert'] },
|
|
{ hash: 'd', hops: 1, tags: ['flood', 'scoped', 'advert'] },
|
|
];
|
|
|
|
test('filters follow the firmware limits: flood.max all, .unscoped and .advert by tag', () => {
|
|
assert.deepStrictEqual(Array.from(H.filterHops(packets, 'flood')), [0, 2, 5, 1]);
|
|
assert.deepStrictEqual(Array.from(H.filterHops(packets, 'flood_unscoped')), [0, 5]);
|
|
assert.deepStrictEqual(Array.from(H.filterHops(packets, 'flood_adverts')), [5, 1]);
|
|
assert.deepStrictEqual(Array.from(H.filterHops(packets, 'bogus')), [0, 2, 5, 1], 'unknown filter falls back to flood');
|
|
});
|
|
|
|
test('histogram has one bucket per hop count from 0 to max', () => {
|
|
assert.deepStrictEqual(Array.from(H.hopHistogram([0, 1, 1, 3])), [1, 2, 0, 1]);
|
|
assert.deepStrictEqual(Array.from(H.hopHistogram([])), []);
|
|
});
|
|
|
|
test('box stats: interpolated quartiles, 1.5 IQR whiskers, outliers counted', () => {
|
|
// 8 lies between the 1.5 IQR fence (6) and a 3 IQR fence (9), so the
|
|
// fence factor decides whether it is an outlier.
|
|
const s = H.hopBoxStats([8, 0, 1, 3, 1]);
|
|
assert.strictEqual(s.n, 5);
|
|
assert.strictEqual(s.min, 0);
|
|
assert.strictEqual(s.q1, 1);
|
|
assert.strictEqual(s.median, 1);
|
|
assert.strictEqual(s.q3, 3);
|
|
assert.strictEqual(s.max, 8);
|
|
assert.strictEqual(s.whiskerLow, 0);
|
|
assert.strictEqual(s.whiskerHigh, 3, 'upper fence is q3 + 1.5*IQR = 6, so the whisker stops at 3');
|
|
assert.strictEqual(s.outliers, 1);
|
|
|
|
const even = H.hopBoxStats([4, 1, 3, 2]);
|
|
assert.strictEqual(even.q1, 1.75);
|
|
assert.strictEqual(even.median, 2.5);
|
|
assert.strictEqual(even.q3, 3.25);
|
|
assert.strictEqual(H.hopBoxStats([]), null);
|
|
});
|
|
|
|
test('section render: firmware-named chips, active filter, summary and ambiguous note', () => {
|
|
const html = H.renderHopSection({ packets, ambiguous: 2 }, 'flood_adverts');
|
|
assert.ok(html.includes('>flood.max<'), 'flood.max chip');
|
|
assert.ok(html.includes('>flood.max.unscoped<'), 'flood.max.unscoped chip');
|
|
assert.ok(html.includes('>flood.max.advert<'), 'flood.max.advert chip');
|
|
assert.ok(/data-hop-filter="flood_adverts"[^>]*aria-pressed="true"/.test(html), 'selected chip is pressed');
|
|
assert.ok(/data-hop-filter="flood"[^>]*aria-pressed="false"/.test(html), 'other chips are not pressed');
|
|
assert.ok(html.includes('2 packets'), 'packet count for the advert filter');
|
|
assert.ok(html.includes('median 3'), 'median of [5, 1]');
|
|
assert.ok(html.includes('max 5'), 'max of [5, 1]');
|
|
assert.ok(html.includes('id="hopCountChart"'), 'chart canvas');
|
|
assert.ok(html.includes('2 packets left out'), 'ambiguous packets are reported');
|
|
});
|
|
|
|
test('section render: empty filter shows a message instead of a chart', () => {
|
|
const html = H.renderHopSection({ packets: [packets[1]], ambiguous: 0 }, 'flood_unscoped');
|
|
assert.ok(!html.includes('id="hopCountChart"'), 'no canvas without data');
|
|
assert.ok(html.includes('No forwarded'), 'empty message');
|
|
assert.ok(!html.includes('left out'), 'no ambiguous note when zero');
|
|
});
|
|
|
|
console.log('node-hop-analytics: ' + passed + ' tests passed');
|