Files
meshcore-analyzer/tests/unit/test-node-reach-coverage-debounce.js
T
Alex B 893773338e chore(tests): move root test-*.js into tests/unit and tests/e2e (#2036)
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.
2026-09-17 19:06:07 +02:00

75 lines
3.1 KiB
JavaScript

'use strict';
const REPO_ROOT = require('path').resolve(__dirname, '..', '..');
// Unit test for #6: pan/zoom coverage redraws must be debounced so dragging the
// map fires at most one /api/...rx-coverage request per settle, not one per
// moveend. We load node-reach-coverage.js in a vm sandbox with controllable
// timers + the real debounce, bind the layer, fire a burst of map events, then
// advance the fake clock and assert exactly one extra fetch happened.
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-reach-coverage.js'), 'utf8');
// Controllable timer queue.
let now = 0;
let nextId = 1;
let timers = [];
function setTimeoutStub(fn, ms) { const id = nextId++; timers.push({ id: id, fn: fn, at: now + (ms || 0) }); return id; }
function clearTimeoutStub(id) { timers = timers.filter(function (t) { return t.id !== id; }); }
function advance(ms) {
now += ms;
const due = timers.filter(function (t) { return t.at <= now; });
timers = timers.filter(function (t) { return t.at > now; });
due.forEach(function (t) { t.fn(); });
}
let fetchCount = 0;
function fakeFetch() {
fetchCount++;
// Chainable stub whose then/catch never invoke callbacks (we only count calls).
const chain = { then: function () { return chain; }, catch: function () { return chain; } };
return chain;
}
const fakeGroup = { addTo: function () { return fakeGroup; }, clearLayers: function () {}, };
const map = {
handlers: {},
on: function (ev, fn) { this.handlers[ev] = fn; },
off: function () {},
getBounds: function () { return { getSouth: function () { return 0; }, getWest: function () { return 0; }, getNorth: function () { return 1; }, getEast: function () { return 1; } }; },
getZoom: function () { return 10; },
removeLayer: function () {},
};
const sandbox = {
window: {},
console: { warn: function () {} },
setTimeout: setTimeoutStub,
clearTimeout: clearTimeoutStub,
fetch: fakeFetch,
L: { layerGroup: function () { return fakeGroup; } },
getComputedStyle: function () { return { getPropertyValue: function () { return ''; } }; },
document: { documentElement: {} },
};
vm.createContext(sandbox);
// Define debounce IN the context so it uses the controllable timers above.
vm.runInContext('function debounce(fn, ms){var t; return function(){var a=arguments, c=this; clearTimeout(t); t=setTimeout(function(){fn.apply(c,a);}, ms);};}', sandbox);
vm.runInContext(code, sandbox);
const handle = sandbox.window.NodeReachCoverage.addLayer(map, 'aabbccddeeff');
assert.strictEqual(fetchCount, 1, 'addLayer should fetch once initially');
// Burst of pan/zoom events.
const fire = map.handlers['moveend zoomend'];
assert.strictEqual(typeof fire, 'function', 'moveend/zoomend handler must be bound');
for (let i = 0; i < 6; i++) fire();
assert.strictEqual(fetchCount, 1, 'burst must not fetch immediately (debounced)');
advance(200);
assert.strictEqual(fetchCount, 2, 'after settle, exactly one coalesced fetch (got ' + fetchCount + ')');
handle.off();
console.log('node-reach-coverage debounce OK');