Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog. Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering. Publish verified Windows artifacts and refreshed English documentation.
134 lines
3.9 KiB
JavaScript
134 lines
3.9 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { makeThrottleTimer } = require('../lib/throttle-timer');
|
|
|
|
function fakeClock() {
|
|
let t = 0;
|
|
let timers = [];
|
|
return {
|
|
now: () => t,
|
|
schedule: (cb, ms) => {
|
|
const h = { at: t + ms, cb, dead: false };
|
|
timers.push(h);
|
|
return h;
|
|
},
|
|
clear: (h) => { if (h) h.dead = true; },
|
|
advance: (ms) => {
|
|
const target = t + ms;
|
|
for (;;) {
|
|
let next = null;
|
|
for (const h of timers) {
|
|
if (!h.dead && h.at <= target && (next === null || h.at < next.at)) next = h;
|
|
}
|
|
if (!next) break;
|
|
t = next.at;
|
|
next.dead = true;
|
|
next.cb();
|
|
}
|
|
t = target;
|
|
timers = timers.filter(h => !h.dead);
|
|
}
|
|
};
|
|
}
|
|
|
|
test('idle debounce: fires once after the delay window', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
tt.request(() => fired++, 500);
|
|
c.advance(499);
|
|
assert.equal(fired, 0);
|
|
c.advance(1);
|
|
assert.equal(fired, 1);
|
|
});
|
|
|
|
test('idle debounce: rapid requests reset the timer (last wins)', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
tt.request(() => fired++, 500);
|
|
c.advance(200);
|
|
tt.request(() => fired++, 500);
|
|
c.advance(300);
|
|
assert.equal(fired, 0, 'should not fire at original 500 — was reset');
|
|
c.advance(200);
|
|
assert.equal(fired, 1, 'fires 500ms after the second request');
|
|
});
|
|
|
|
test('STARVATION repro: continuous requests with no maxWait NEVER fire', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
for (let i = 0; i < 30; i++) {
|
|
tt.request(() => fired++, 500);
|
|
c.advance(100);
|
|
}
|
|
assert.equal(fired, 0, 'this is exactly the bug the maxWait fix addresses');
|
|
});
|
|
|
|
test('maxWait: continuous requests still force a fire within the window', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
const fireAtTimes = [];
|
|
for (let i = 0; i < 30; i++) {
|
|
tt.request(() => { fired++; fireAtTimes.push(c.now()); }, 500, 2000);
|
|
c.advance(100);
|
|
}
|
|
assert.ok(fired >= 1, 'maxWait guarantees at least one fire under continuous load');
|
|
assert.ok(fireAtTimes.every(t => t > 0), 'fires happened, not starved');
|
|
assert.ok(fireAtTimes.some(t => t <= 2000), 'first fire no later than maxWait');
|
|
});
|
|
|
|
test('maxWait: after a forced fire a fresh burst starts (periodic fires)', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
for (let i = 0; i < 60; i++) {
|
|
tt.request(() => fired++, 500, 2000);
|
|
c.advance(100);
|
|
}
|
|
assert.ok(fired >= 2, `~6000ms of continuous load with 2000ms maxWait should fire multiple times, got ${fired}`);
|
|
});
|
|
|
|
test('flushSync fires the pending fn immediately and clears it', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
tt.request(() => fired++, 5000);
|
|
assert.ok(tt.isPending());
|
|
tt.flushSync();
|
|
assert.equal(fired, 1);
|
|
assert.ok(!tt.isPending());
|
|
c.advance(10000);
|
|
assert.equal(fired, 1, 'no double fire after flushSync');
|
|
});
|
|
|
|
test('cancel drops the pending fn — no fire', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
let fired = 0;
|
|
tt.request(() => fired++, 500);
|
|
tt.cancel();
|
|
assert.ok(!tt.isPending());
|
|
c.advance(10000);
|
|
assert.equal(fired, 0);
|
|
});
|
|
|
|
test('last-write-wins: a later request with a DIFFERENT fn replaces the earlier one', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
const fired = [];
|
|
tt.request(() => fired.push('persist'), 500, 20000);
|
|
c.advance(100);
|
|
tt.request(() => fired.push('clear'), 0);
|
|
c.advance(100);
|
|
assert.deepEqual(fired, ['clear'], 'only the latest fn fires; the persist was dropped');
|
|
});
|
|
|
|
test('flushSync with nothing pending is a no-op', () => {
|
|
const c = fakeClock();
|
|
const tt = makeThrottleTimer(c);
|
|
assert.doesNotThrow(() => tt.flushSync());
|
|
});
|